/*-------------------------------------------------------------------------
 *
 * ruleutils.c
 *	  将存储的表达式/查询树转换回源文本的函数
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/ruleutils.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>

#include "access/amapi.h"
#include "access/htup_details.h"
#include "access/relation.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_am.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_language.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/tablespace.h"
#include "common/keywords.h"
#include "executor/spi.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/pathnodes.h"
#include "optimizer/optimizer.h"
#include "parser/parse_agg.h"
#include "parser/parse_func.h"
#include "parser/parse_node.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "parser/parser.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteSupport.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/hsearch.h"
#include "utils/lsyscache.h"
#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/ruleutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
#include "utils/varlena.h"
#include "utils/xml.h"

/* ----------
 * 美观格式常量
 * ----------
 */

/*缩进计数*/
#define PRETTYINDENT_STD		8
#define PRETTYINDENT_JOIN		4
#define PRETTYINDENT_VAR		4

#define PRETTYINDENT_LIMIT		40	/*换行限制*/

/*漂亮标志*/
#define PRETTYFLAG_PAREN		0x0001
#define PRETTYFLAG_INDENT		0x0002
#define PRETTYFLAG_SCHEMA		0x0004

/*将“bool pretty”选项标准转换为详细标志*/
#define GET_PRETTY_FLAGS(pretty) \
	((pretty) ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) \
	 : PRETTYFLAG_INDENT)

/*默认的漂亮打印换行的行长度：0表示始终换行*/
#define WRAP_COLUMN_DEFAULT		0

/*测试漂亮操作需要的宏*/
#define PRETTY_PAREN(context)	((context)->prettyFlags & PRETTYFLAG_PAREN)
#define PRETTY_INDENT(context)	((context)->prettyFlags & PRETTYFLAG_INDENT)
#define PRETTY_SCHEMA(context)	((context)->prettyFlags & PRETTYFLAG_SCHEMA)


/*---------- 
 * 本地数据类型 
 * ---------- 
 */ 

/*调用递归querytree显示例程所需的上下文信息*/
typedef struct
{
	StringInfo	buf;			/* 用于附加的输出缓冲区 */
	List	   *namespaces;		/*deparse_namespace节点列表*/
	List	   *windowClause;	/*当前查询级别的WINDOW子句*/
	List	   *windowTList;	/*解决WINDOW子句的目标列表*/
	int			prettyFlags;	/*启用漂亮打印功能*/
	int			wrapColumn;		/*最大行长度，或-1表示无限制*/
	int			indentLevel;	/*当前漂亮打印的缩进级别*/
	bool		varprefix;		/*如果为真，则在变量上打印前缀*/
	ParseExprKind special_exprkind; /*仅对需要特殊处理的exprkinds设置*/
	Bitmapset  *appendparents;	/*如果不为null，将这些relids的子变量
 * 映射回父关系*/
} deparse_context;

 
 /*每个子树周围的查询上下文级别需要一个Var命名空间级别。
 * Var的varlevelsup=N指的是当前上下文的命名空间列表中的第N个项目（从0开始计数）。
 *
 * rtable是查询或PlannedStmt中的实际RTE列表。
 * rtable_names保存要用于每个RTE的别名（要么是C字符串，要么是NULL，用于没有名称的RTE，例如没有名称的连接）。
 * rtable_columns保存要用于每个RTE的列别名。
 *
 * subplans是针对SubPlans和CTEs的计划树列表（仅在PlannedStmt情况下使用）。
 * ctes是CommonTableExpr节点的列表（仅在查询情况下使用）。
 * appendrels如果不为null（仅在PlannedStmt情况下使用），是按子关系ID索引的AppendRelInfo节点的数组。我们用它将子表变量映射到它们的继承父级。
 *
 * 在某些情况下，我们需要使合并的JOIN USING列的名称在整个查询中是唯一的，而不仅仅是每个RTE。如果是这样，unique_using为真，
 * using_names是表示已经分配给USING列的名称的C字符串列表。
 *
 * 在反解析计划树时，deparse_namespace列表中始终只有一个项目（因为计划树永远不会包含具有varlevelsup > 0的变量）。我们存储的是要反解析的表达式的直接
 * 父级的Plan节点，以及该Plan的祖先列表。此外，我们存储它的外部和内部子计划节点，以及它们的目标列表，以及索引tlist如果当前计划节点
 * 可能包含INDEX_VAR变量。（这些字段可以从当前Plan节点动态推导，但按单独字段设置似乎更清晰。）
 */ 
typedef struct
{
	List	   *rtable;			/*RangeTblEntry节点列表*/
	List	   *rtable_names;	/*RTE的名称并行列表*/
	List	   *rtable_columns; /*deparse_columns结构的并行列表*/
	List	   *subplans;		/*SubPlans的计划树列表*/
	List	   *ctes;			/*CommonTableExpr节点的列表*/
	AppendRelInfo **appendrels; /*AppendRelInfo节点的数组，或NULL*/
	/*用于列别名分配的工作区：*/
	bool		unique_using;	/*我们在全球范围内使USING名称唯一吗*/
	List	   *using_names;	/*已分配的USING列名称列表*/
	/*其余字段仅在反解析计划树时使用：*/
	Plan	   *plan;			/*当前表达式的直接父级*/
	List	   *ancestors;		/*计划的祖先*/
	Plan	   *outer_plan;		/* 外部子节点，如果没有则为 NULL */
	Plan	   *inner_plan;		/* 内部子节点，如果没有则为 NULL */
	List	   *outer_tlist;	/* OUTER_VAR 变量的引用 */
	List	   *inner_tlist;	/* INNER_VAR 变量的引用 */
	List	   *index_tlist;	/* INDEX_VAR 变量的引用 */
	/* 表示函数签名的特殊命名空间： */
	char	   *funcname;
	int			numargs;
	char	  **argnames;
} deparse_namespace;

/*
 * 关于列别名的每个关系的数据。
 *
 * 选择别名的过程非常复杂，因为需要导出
 * 规则/视图，其基础表在查询解析之后可能已经添加、删除或
 * 重命名列。我们必须仍然以一种可以重新加载的形式打印
 * 规则/视图，并能产生与之前相同的结果。
 *
 * 对于查询中使用的每个 RTE，我们必须分配在该 RTE 内唯一的列别名。
 * SQL 不要求原始查询这样做，但由于 *-扩展等因素，我们需要能够唯一
 * 引用在反编译查询中的每一列。只要我们限定所有
 * 列引用，每个 RTE 内的唯一性就足够了。
 *
 * 然而，我们无法确保对于未命名连接 RTE 的每个列名的唯一性，
 * 因为它们仅仅继承其输入 RTE 的列名，而且我们无法
 * 在连接层重命名列。大多数时候这不是一个问题，
 * 因为我们不需要作为这样的参考连接的输出列；我们
 * 可以代替引用输入列。然而，这种方法对于合并的
 * JOIN USING 列可能会失败，因此当我们在一个未命名
 * 的连接中遇到其中一个列时，我们必须使该列的别名在整个
 * 查询中全局唯一，以确保可以明确引用。
 *
 * 另一个问题是，JOIN USING 子句要求被合并的列
 * 在两个输入 RTE 中具有相同的别名，并且这两个 RTE 或
 * 其子级中的其他列不能与 USING 名称冲突。
 * 为了解决这个问题，我们在查询的 jointree 中递归地进行
 * USING 列别名的分配。当通过带有 USING 的 JOIN 向下
 * 递归时，我们预先分配 USING 列名给子列，覆盖
 * 列别名分配的其他规则。我们还会为每个 RTE 标记一个列表，
 * 其中包含为包含该 RTE 的连接选择的所有 USING 列名，
 * 这样当我们稍后分配其他列的别名时，就可以避免冲突。
 *
 * 另一个问题是，如果 JOIN 的输入表自查询解析以来添加或
 * 删除了列，我们必须为连接生成一个与当前输入列集合匹配的列
 * 别名列表——否则，左侧输入的列数变化将影响
 * 别名与右侧输入的列的匹配。因此，在可打印的列
 * 别名列表中的位置不一定与 JOIN 的 varattnos 一一对应，
 * 因此我们需要一个单独的 new_colnames[] 数组用于打印。
 */
typedef struct
{
	/*
	 * colnames 是一个数组，包含用于在查询解析时
	 * 存在的列的列别名。删除的列有 NULL 项。
	 * 此数组可以通过 varattno 直接索引以获取 Var 的名称。
	 *
	 * 非 NULL 项在 RTE 内保证唯一，*除了* 
	 * 这是针对未命名的 JOIN RTE。在这种情况下，我们仅仅从
	 * 两个输入 RTE 中复制名称。
	 *
	 * 在 set_using_names() 中的递归下降过程中，强制分配
	 * 子 RTE 的列名通过预先设置该子项的 colnames 数组中的元素表示。
	 * 因此在该阶段，该数组中的 NULL 项只意味着未预分配名称，
	 * 不一定意味着列被删除。
	 */
	int			num_cols;		/* colnames[] 数组的长度 */
	char	  **colnames;		/* C 字符串和 NULL 的数组 */

	/*
	 * new_colnames 是一个数组，包含用于在查询
	 * 重新解析时如果存在的列的列别名，基于
	 * 当前基础表的定义。这是作为 RTE 的列
	 * 别名列表要打印的内容。该数组不包括已删除的列，
	 * 但将包含自原始解析以来添加的列。因此，
	 * 其中的索引与当前 varattno 值几乎没有关系。如上所述，
	 * 除非这是针对未命名的 JOIN RTE，否则条目是唯一的。
	 * （在这样的 RTE 中，我们实际上从不打印此数组，
	 * 但我们必须计算它以便可能用于计算上层连接的列名。）并行数组
	 * is_new_col 标记这些列中哪些是自
	 * 原始解析以来的新列。具有 is_new_col false 的条目必须
	 * 与非 NULL colnames 条目一一匹配。
	 */
	int			num_new_cols;	/* new_colnames[] 数组的长度 */
	char	  **new_colnames;	/* C 字符串的数组 */
	bool	   *is_new_col;		/* 布尔标志数组 */

	/* 此标志指示我们是否应真正打印列别名列表 */
	bool		printaliases;

	/* 此列表包含所有在该 RTE 之上连接中用作 USING 名称的名称 */
	List	   *parentUsing;	/* 分配给父合并列的名称 */

	
/*
	 * 如果这个结构体是用于 JOIN RTE，我们将在 set_using_names() 过程中填充这些字段，以描述它与其子 RTE 之间的关系。
	 *
	 * leftattnos 和 rightattnos 是每个现有的 JOIN 输出列有一个条目的数组（因此，可以通过 join varattno 来索引）。对于左子节点列的简单引用，leftattnos[i] 是子 RTE 的 attno，而 rightattnos[i] 是零；反之，对于右子节点的列。对于通过 JOIN USING/NATURAL JOIN 生成的合并列，leftattnos[i] 和 rightattnos[i] 都是非零的。
	 * 请注意，简单引用可能指向已被删除的子 RTE 列；但这没关系，因为该列不能在查询中使用。
	 *
	 * 如果是 JOIN USING，usingNames 包含为合并列选择的别名（如果我们必须修改名称以实现唯一性，这些可能与原始 USING 列表不同）。
	 */
	int			leftrti;		/* 左子节点的 rangetable 索引 */
	int			rightrti;		/* 右子节点的 rangetable 索引 */
	int		   *leftattnos;		/* join 列的左子节点 varattnos，或 0 */
	int		   *rightattnos;	/* join 列的右子节点 varattnos，或 0 */
	List	   *usingNames;		/* 分配给合并列的名称 */
} deparse_columns;

/* 该宏类似于 rt_fetch()，但用于 deparse_columns 结构体 */
#define deparse_columns_fetch(rangetable_index, dpns) \
	((deparse_columns *) list_nth((dpns)->rtable_columns, (rangetable_index)-1))

/*
 * set_rtable_names 的哈希表中的条目
 */
typedef struct
{
	char		name[NAMEDATALEN];	/* 哈希键 --- 必须是第一个 */
	int			counter;		/* 迄今为止用于名称的最大添加 */
} NameHashEntry;

/* resolve_special_varno() 的回调签名 */
typedef void (*rsv_callback) (Node *node, deparse_context *context,
							  void *callback_arg);


/* ----------
 * 全局数据
 * ----------
 */
static SPIPlanPtr plan_getrulebyoid = NULL;
static const char *query_getrulebyoid = "SELECT * FROM pg_catalog.pg_rewrite WHERE oid = $1";
static SPIPlanPtr plan_getviewrule = NULL;
static const char *query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND rulename = $2";

/* GUC 参数 */
bool		quote_all_identifiers = false;


/* ----------
 * 本地函数
 *
 * 这些函数大多数以前使用固定大小的缓冲区来构建它们的结果。现在，它们接受一个（已经初始化的）StringInfo 对象作为参数，并将它们的文本输出追加到其内容中。
 * ----------
 */
static char *fc_deparse_expression_pretty(Node *fc_expr, List *fc_dpcontext,
									   bool fc_forceprefix, bool fc_showimplicit,
									   int fc_prettyFlags, int fc_startIndent);
static char *pg_get_viewdef_worker(Oid fc_viewoid,
								   int fc_prettyFlags, int fc_wrapColumn);
static char *pg_get_triggerdef_worker(Oid fc_trigid, bool fc_pretty);
static int	fc_decompile_column_index_array(Datum fc_column_index_array, Oid fc_relId,
										 StringInfo fc_buf);
static char *pg_get_ruledef_worker(Oid fc_ruleoid, int fc_prettyFlags);
static char *pg_get_indexdef_worker(Oid fc_indexrelid, int fc_colno,
									const Oid *fc_excludeOps,
									bool fc_attrsOnly, bool fc_keysOnly,
									bool fc_showTblSpc, bool fc_inherits,
									int fc_prettyFlags, bool fc_missing_ok);
static char *pg_get_statisticsobj_worker(Oid fc_statextid, bool fc_columns_only,
										 bool fc_missing_ok);
static char *pg_get_partkeydef_worker(Oid fc_relid, int fc_prettyFlags,
									  bool fc_attrsOnly, bool fc_missing_ok);
static char *pg_get_constraintdef_worker(Oid fc_constraintId, bool fc_fullCommand,
										 int fc_prettyFlags, bool fc_missing_ok);
static text *pg_get_expr_worker(text *fc_expr, Oid fc_relid, int fc_prettyFlags);
static int	fc_print_function_arguments(StringInfo fc_buf, HeapTuple fc_proctup,
									 bool fc_print_table_args, bool fc_print_defaults);
static void fc_print_function_rettype(StringInfo fc_buf, HeapTuple fc_proctup);
static void fc_print_function_trftypes(StringInfo fc_buf, HeapTuple fc_proctup);
static void fc_print_function_sqlbody(StringInfo fc_buf, HeapTuple fc_proctup);
static void set_rtable_names(deparse_namespace *fc_dpns, List *fc_parent_namespaces,
							 Bitmapset *fc_rels_used);
static void set_deparse_for_query(deparse_namespace *fc_dpns, Query *fc_query,
								  List *fc_parent_namespaces);
static void set_simple_column_names(deparse_namespace *fc_dpns);
static bool fc_has_dangerous_join_using(deparse_namespace *fc_dpns, Node *fc_jtnode);
static void set_using_names(deparse_namespace *fc_dpns, Node *fc_jtnode,
							List *fc_parentUsing);
static void set_relation_column_names(deparse_namespace *fc_dpns,
									  RangeTblEntry *fc_rte,
									  deparse_columns *fc_colinfo);
static void set_join_column_names(deparse_namespace *fc_dpns, RangeTblEntry *fc_rte,
								  deparse_columns *fc_colinfo);
static bool fc_colname_is_unique(const char *fc_colname, deparse_namespace *fc_dpns,
							  deparse_columns *fc_colinfo);
static char *fc_make_colname_unique(char *fc_colname, deparse_namespace *fc_dpns,
								 deparse_columns *fc_colinfo);
static void fc_expand_colnames_array_to(deparse_columns *fc_colinfo, int fc_n);
static void fc_identify_join_columns(JoinExpr *fc_j, RangeTblEntry *fc_jrte,
								  deparse_columns *fc_colinfo);
static char *get_rtable_name(int fc_rtindex, deparse_context *fc_context);
static void set_deparse_plan(deparse_namespace *fc_dpns, Plan *fc_plan);
static Plan *fc_find_recursive_union(deparse_namespace *fc_dpns,
								  WorkTableScan *fc_wtscan);
static void fc_push_child_plan(deparse_namespace *fc_dpns, Plan *fc_plan,
							deparse_namespace *fc_save_dpns);
static void fc_pop_child_plan(deparse_namespace *fc_dpns,
						   deparse_namespace *fc_save_dpns);
static void fc_push_ancestor_plan(deparse_namespace *fc_dpns, ListCell *fc_ancestor_cell,
							   deparse_namespace *fc_save_dpns);
static void fc_pop_ancestor_plan(deparse_namespace *fc_dpns,
							  deparse_namespace *fc_save_dpns);
static void fc_make_ruledef(StringInfo fc_buf, HeapTuple fc_ruletup, TupleDesc fc_rulettc,
						 int fc_prettyFlags);
static void fc_make_viewdef(StringInfo fc_buf, HeapTuple fc_ruletup, TupleDesc fc_rulettc,
						 int fc_prettyFlags, int fc_wrapColumn);
static void get_query_def(Query *fc_query, StringInfo fc_buf, List *fc_parentnamespace,
						  TupleDesc fc_resultDesc, bool fc_colNamesVisible,
						  int fc_prettyFlags, int fc_wrapColumn, int fc_startIndent);
static void get_values_def(List *fc_values_lists, deparse_context *fc_context);
static void get_with_clause(Query *fc_query, deparse_context *fc_context);
static void get_select_query_def(Query *fc_query, deparse_context *fc_context,
								 TupleDesc fc_resultDesc, bool fc_colNamesVisible);
static void get_insert_query_def(Query *fc_query, deparse_context *fc_context,
								 bool fc_colNamesVisible);
static void get_update_query_def(Query *fc_query, deparse_context *fc_context,
								 bool fc_colNamesVisible);
static void get_update_query_targetlist_def(Query *fc_query, List *fc_targetList,
											deparse_context *fc_context,
											RangeTblEntry *fc_rte);
static void get_delete_query_def(Query *fc_query, deparse_context *fc_context,
								 bool fc_colNamesVisible);
static void get_merge_query_def(Query *fc_query, deparse_context *fc_context,
								bool fc_colNamesVisible);
static void get_utility_query_def(Query *fc_query, deparse_context *fc_context);
static void get_basic_select_query(Query *fc_query, deparse_context *fc_context,
								   TupleDesc fc_resultDesc, bool fc_colNamesVisible);
static void get_target_list(List *fc_targetList, deparse_context *fc_context,
							TupleDesc fc_resultDesc, bool fc_colNamesVisible);
static void get_setop_query(Node *fc_setOp, Query *fc_query,
							deparse_context *fc_context,
							TupleDesc fc_resultDesc, bool fc_colNamesVisible);
static Node *get_rule_sortgroupclause(Index fc_ref, List *fc_tlist,
									  bool fc_force_colno,
									  deparse_context *fc_context);
static void get_rule_groupingset(GroupingSet *fc_gset, List *fc_targetlist,
								 bool fc_omit_parens, deparse_context *fc_context);
static void get_rule_orderby(List *fc_orderList, List *fc_targetList,
							 bool fc_force_colno, deparse_context *fc_context);
static void get_rule_windowclause(Query *fc_query, deparse_context *fc_context);
static void get_rule_windowspec(WindowClause *fc_wc, List *fc_targetList,
								deparse_context *fc_context);
static char *get_variable(Var *fc_var, int fc_levelsup, bool fc_istoplevel,
						  deparse_context *fc_context);
static void get_special_variable(Node *fc_node, deparse_context *fc_context,
								 void *fc_callback_arg);
static void fc_resolve_special_varno(Node *fc_node, deparse_context *fc_context,
								  rsv_callback fc_callback, void *fc_callback_arg);
static Node *fc_find_param_referent(Param *fc_param, deparse_context *fc_context,
								 deparse_namespace **fc_dpns_p, ListCell **fc_ancestor_cell_p);
static void get_parameter(Param *fc_param, deparse_context *fc_context);
static const char *get_simple_binary_op_name(OpExpr *fc_expr);
static bool fc_isSimpleNode(Node *fc_node, Node *fc_parentNode, int fc_prettyFlags);
static void fc_appendContextKeyword(deparse_context *fc_context, const char *fc_str,
								 int fc_indentBefore, int fc_indentAfter, int fc_indentPlus);
static void fc_removeStringInfoSpaces(StringInfo fc_str);
static void get_rule_expr(Node *fc_node, deparse_context *fc_context,
						  bool fc_showimplicit);
static void get_rule_expr_toplevel(Node *fc_node, deparse_context *fc_context,
								   bool fc_showimplicit);
static void get_rule_list_toplevel(List *fc_lst, deparse_context *fc_context,
								   bool fc_showimplicit);
static void get_rule_expr_funccall(Node *fc_node, deparse_context *fc_context,
								   bool fc_showimplicit);
static bool fc_looks_like_function(Node *fc_node);
static void get_oper_expr(OpExpr *fc_expr, deparse_context *fc_context);
static void get_func_expr(FuncExpr *fc_expr, deparse_context *fc_context,
						  bool fc_showimplicit);
static void get_agg_expr(Aggref *fc_aggref, deparse_context *fc_context,
						 Aggref *fc_original_aggref);
static void get_agg_combine_expr(Node *fc_node, deparse_context *fc_context,
								 void *fc_callback_arg);
static void get_windowfunc_expr(WindowFunc *fc_wfunc, deparse_context *fc_context);
static bool get_func_sql_syntax(FuncExpr *fc_expr, deparse_context *fc_context);
static void get_coercion_expr(Node *fc_arg, deparse_context *fc_context,
							  Oid fc_resulttype, int32 fc_resulttypmod,
							  Node *fc_parentNode);
static void get_const_expr(Const *fc_constval, deparse_context *fc_context,
						   int fc_showtype);
static void get_const_collation(Const *fc_constval, deparse_context *fc_context);
static void fc_simple_quote_literal(StringInfo fc_buf, const char *fc_val);
static void get_sublink_expr(SubLink *fc_sublink, deparse_context *fc_context);
static void get_tablefunc(TableFunc *fc_tf, deparse_context *fc_context,
						  bool fc_showimplicit);
static void get_from_clause(Query *fc_query, const char *fc_prefix,
							deparse_context *fc_context);
static void get_from_clause_item(Node *fc_jtnode, Query *fc_query,
								 deparse_context *fc_context);
static void get_rte_alias(RangeTblEntry *fc_rte, int fc_varno, bool fc_use_as,
						  deparse_context *fc_context);
static void get_column_alias_list(deparse_columns *fc_colinfo,
								  deparse_context *fc_context);
static void get_from_clause_coldeflist(RangeTblFunction *fc_rtfunc,
									   deparse_columns *fc_colinfo,
									   deparse_context *fc_context);
static void get_tablesample_def(TableSampleClause *fc_tablesample,
								deparse_context *fc_context);
static void get_opclass_name(Oid fc_opclass, Oid fc_actual_datatype,
							 StringInfo fc_buf);
static Node *fc_processIndirection(Node *fc_node, deparse_context *fc_context);
static void fc_printSubscripts(SubscriptingRef *fc_sbsref, deparse_context *fc_context);
static char *get_relation_name(Oid fc_relid);
static char *fc_generate_relation_name(Oid fc_relid, List *fc_namespaces);
static char *fc_generate_qualified_relation_name(Oid fc_relid);
static char *fc_generate_function_name(Oid fc_funcid, int fc_nargs,
									List *fc_argnames, Oid *fc_argtypes,
									bool fc_has_variadic, bool *fc_use_variadic_p,
									ParseExprKind fc_special_exprkind);
static char *fc_generate_operator_name(Oid fc_operid, Oid fc_arg1, Oid fc_arg2);
static void add_cast_to(StringInfo fc_buf, Oid fc_typid);
static char *fc_generate_qualified_type_name(Oid fc_typid);
static text *fc_string_to_text(char *fc_str);
static char *fc_flatten_reloptions(Oid fc_relid);
static void get_reloptions(StringInfo fc_buf, Datum fc_reloptions);

#define only_marker(rte)  ((rte)->inh ? "" : "ONLY ")


/**********
 * pg_get_ruledef - 执行所有操作并返回一个可以用于重新创建规则的文本
 **********/
Datum pg_get_ruledef(PG_FUNCTION_ARGS)
{
	Oid			fc_ruleoid = PG_GETARG_OID(0);
	int			fc_prettyFlags;
	char	   *fc_res;

	fc_prettyFlags = PRETTYFLAG_INDENT;

	fc_res = pg_get_ruledef_worker(fc_ruleoid, fc_prettyFlags);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}


Datum pg_get_ruledef_ext(PG_FUNCTION_ARGS)
{
	Oid			fc_ruleoid = PG_GETARG_OID(0);
	bool		fc_pretty = PG_GETARG_BOOL(1);
	int			fc_prettyFlags;
	char	   *fc_res;

	fc_prettyFlags = GET_PRETTY_FLAGS(fc_pretty);

	fc_res = pg_get_ruledef_worker(fc_ruleoid, fc_prettyFlags);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}


static char * pg_get_ruledef_worker(Oid fc_ruleoid, int fc_prettyFlags)
{
	Datum		fc_args[1];
	char		fc_nulls[1];
	int			fc_spirc;
	HeapTuple	fc_ruletup;
	TupleDesc	fc_rulettc;
	StringInfoData fc_buf;

	/*
	 * 首先执行此操作，以便在外部上下文中分配字符串，而不是在SPI中。
	 */
	initStringInfo(&fc_buf);

	/*
	 * 连接到 SPI 管理器
	 */
	if (SPI_connect() != SPI_OK_CONNECT)
		elog(ERROR, "SPI_connect failed");

	/*
	 * 在第一次调用时准备计划以查找pg_rewrite。我们通过SPI管理器读取pg_rewrite，而不是使用系统缓存来检查对pg_rewrite的读取访问权限。
	 */
	if (plan_getrulebyoid == NULL)
	{
		Oid			fc_argtypes[1];
		SPIPlanPtr	fc_plan;

		fc_argtypes[0] = OIDOID;
		fc_plan = SPI_prepare(query_getrulebyoid, 1, fc_argtypes);
		if (fc_plan == NULL)
			elog(ERROR, "SPI_prepare failed for \"%s\"", query_getrulebyoid);
		SPI_keepplan(fc_plan);
		plan_getrulebyoid = fc_plan;
	}

	/*
	 * 获取该规则的pg_rewrite元组
	 */
	fc_args[0] = ObjectIdGetDatum(fc_ruleoid);
	fc_nulls[0] = ' ';
	fc_spirc = SPI_execute_plan(plan_getrulebyoid, fc_args, fc_nulls, true, 0);
	if (fc_spirc != SPI_OK_SELECT)
		elog(ERROR, "failed to get pg_rewrite tuple for rule %u", fc_ruleoid);
	if (SPI_processed != 1)
	{
		/*
		 * 此处没有元组数据可用，只需保持输出缓冲区为空。
		 */
	}
	else
	{
		/*
		 * 获取规则的定义并将其放入执行器的内存中
		 */
		fc_ruletup = SPI_tuptable->vals[0];
		fc_rulettc = SPI_tuptable->tupdesc;
		fc_make_ruledef(&fc_buf, fc_ruletup, fc_rulettc, fc_prettyFlags);
	}

	/*
	 * 与 SPI 管理器断开连接
	 */
	if (SPI_finish() != SPI_OK_FINISH)
		elog(ERROR, "SPI_finish failed");

	if (fc_buf.len == 0)
		return NULL;

	return fc_buf.data;
}


/* ----------
 * pg_get_viewdef		- 主要是相同的内容，但我们
 *				  只返回视图的 SELECT 部分
 * ----------
 */
Datum pg_get_viewdef(PG_FUNCTION_ARGS)
{
	/* 按OID */
	Oid			fc_viewoid = PG_GETARG_OID(0);
	int			fc_prettyFlags;
	char	   *fc_res;

	fc_prettyFlags = PRETTYFLAG_INDENT;

	fc_res = pg_get_viewdef_worker(fc_viewoid, fc_prettyFlags, WRAP_COLUMN_DEFAULT);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}


Datum pg_get_viewdef_ext(PG_FUNCTION_ARGS)
{
	/* 按OID */
	Oid			fc_viewoid = PG_GETARG_OID(0);
	bool		fc_pretty = PG_GETARG_BOOL(1);
	int			fc_prettyFlags;
	char	   *fc_res;

	fc_prettyFlags = GET_PRETTY_FLAGS(fc_pretty);

	fc_res = pg_get_viewdef_worker(fc_viewoid, fc_prettyFlags, WRAP_COLUMN_DEFAULT);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}

Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS)
{
	/* 按OID */
	Oid			fc_viewoid = PG_GETARG_OID(0);
	int			fc_wrap = PG_GETARG_INT32(1);
	int			fc_prettyFlags;
	char	   *fc_res;

	/* 调用此函数意味着我们想要美观打印 */
	fc_prettyFlags = GET_PRETTY_FLAGS(true);

	fc_res = pg_get_viewdef_worker(fc_viewoid, fc_prettyFlags, fc_wrap);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}

Datum pg_get_viewdef_name(PG_FUNCTION_ARGS)
{
	/* 按限定名称 */
	text	   *fc_viewname = PG_GETARG_TEXT_PP(0);
	int			fc_prettyFlags;
	RangeVar   *fc_viewrel;
	Oid			fc_viewoid;
	char	   *fc_res;

	fc_prettyFlags = PRETTYFLAG_INDENT;

	/* 查找视图名称。 不能锁定它 - 我们可能没有权限。 */
	fc_viewrel = makeRangeVarFromNameList(textToQualifiedNameList(fc_viewname));
	fc_viewoid = RangeVarGetRelid(fc_viewrel, NoLock, false);

	fc_res = pg_get_viewdef_worker(fc_viewoid, fc_prettyFlags, WRAP_COLUMN_DEFAULT);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}


Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS)
{
	/* 按限定名称 */
	text	   *fc_viewname = PG_GETARG_TEXT_PP(0);
	bool		fc_pretty = PG_GETARG_BOOL(1);
	int			fc_prettyFlags;
	RangeVar   *fc_viewrel;
	Oid			fc_viewoid;
	char	   *fc_res;

	fc_prettyFlags = GET_PRETTY_FLAGS(fc_pretty);

	/* 查找视图名称。 不能锁定它 - 我们可能没有权限。 */
	fc_viewrel = makeRangeVarFromNameList(textToQualifiedNameList(fc_viewname));
	fc_viewoid = RangeVarGetRelid(fc_viewrel, NoLock, false);

	fc_res = pg_get_viewdef_worker(fc_viewoid, fc_prettyFlags, WRAP_COLUMN_DEFAULT);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}

/*
 * 为 pg_get_viewdef 的 by-OID 和 by-name 变体提供公共代码
 */
static char * pg_get_viewdef_worker(Oid fc_viewoid, int fc_prettyFlags, int fc_wrapColumn)
{
	Datum		fc_args[2];
	char		fc_nulls[2];
	int			fc_spirc;
	HeapTuple	fc_ruletup;
	TupleDesc	fc_rulettc;
	StringInfoData fc_buf;

	/*
	 * 首先执行此操作，以便在外部上下文中分配字符串，而不是在SPI中。
	 */
	initStringInfo(&fc_buf);

	/*
	 * 连接到 SPI 管理器
	 */
	if (SPI_connect() != SPI_OK_CONNECT)
		elog(ERROR, "SPI_connect failed");

	/*
	 * 在第一次调用时准备计划以查找pg_rewrite。我们通过SPI管理器读取pg_rewrite，而不是使用系统缓存来检查对pg_rewrite的读取访问权限。
	 */
	if (plan_getviewrule == NULL)
	{
		Oid			fc_argtypes[2];
		SPIPlanPtr	fc_plan;

		fc_argtypes[0] = OIDOID;
		fc_argtypes[1] = NAMEOID;
		fc_plan = SPI_prepare(query_getviewrule, 2, fc_argtypes);
		if (fc_plan == NULL)
			elog(ERROR, "SPI_prepare failed for \"%s\"", query_getviewrule);
		SPI_keepplan(fc_plan);
		plan_getviewrule = fc_plan;
	}

	/*
	 * 获取视图的 SELECT 规则的 pg_rewrite 元组
	 */
	fc_args[0] = ObjectIdGetDatum(fc_viewoid);
	fc_args[1] = DirectFunctionCall1(namein, CStringGetDatum(ViewSelectRuleName));
	fc_nulls[0] = ' ';
	fc_nulls[1] = ' ';
	fc_spirc = SPI_execute_plan(plan_getviewrule, fc_args, fc_nulls, true, 0);
	if (fc_spirc != SPI_OK_SELECT)
		elog(ERROR, "failed to get pg_rewrite tuple for view %u", fc_viewoid);
	if (SPI_processed != 1)
	{
		/*
		 * 此处没有元组数据可用，只需保持输出缓冲区为空。
		 */
	}
	else
	{
		/*
		 * 获取规则的定义并将其放入执行器的内存中
		 */
		fc_ruletup = SPI_tuptable->vals[0];
		fc_rulettc = SPI_tuptable->tupdesc;
		fc_make_viewdef(&fc_buf, fc_ruletup, fc_rulettc, fc_prettyFlags, fc_wrapColumn);
	}

	/*
	 * 与 SPI 管理器断开连接
	 */
	if (SPI_finish() != SPI_OK_FINISH)
		elog(ERROR, "SPI_finish failed");

	if (fc_buf.len == 0)
		return NULL;

	return fc_buf.data;
}

/* ----------
 * pg_get_triggerdef		- 获取触发器的定义
 * ----------
 */
Datum pg_get_triggerdef(PG_FUNCTION_ARGS)
{
	Oid			fc_trigid = PG_GETARG_OID(0);
	char	   *fc_res;

	fc_res = pg_get_triggerdef_worker(fc_trigid, false);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}

Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS)
{
	Oid			fc_trigid = PG_GETARG_OID(0);
	bool		fc_pretty = PG_GETARG_BOOL(1);
	char	   *fc_res;

	fc_res = pg_get_triggerdef_worker(fc_trigid, fc_pretty);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}

static char * pg_get_triggerdef_worker(Oid fc_trigid, bool fc_pretty)
{
	HeapTuple	fc_ht_trig;
	Form_pg_trigger fc_trigrec;
	StringInfoData fc_buf;
	Relation	fc_tgrel;
	ScanKeyData fc_skey[1];
	SysScanDesc fc_tgscan;
	int			fc_findx = 0;
	char	   *fc_tgname;
	char	   *fc_tgoldtable;
	char	   *fc_tgnewtable;
	Datum		fc_value;
	bool		fc_isnull;

	/*
	 * 通过触发器的 Oid 获取 pg_trigger 元组
	 */
	fc_tgrel = table_open(TriggerRelationId, AccessShareLock);

	ScanKeyInit(&fc_skey[0],
				Anum_pg_trigger_oid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_trigid));

	fc_tgscan = systable_beginscan(fc_tgrel, TriggerOidIndexId, true,
								NULL, 1, fc_skey);

	fc_ht_trig = systable_getnext(fc_tgscan);

	if (!HeapTupleIsValid(fc_ht_trig))
	{
		systable_endscan(fc_tgscan);
		table_close(fc_tgrel, AccessShareLock);
		return NULL;
	}

	fc_trigrec = (Form_pg_trigger) GETSTRUCT(fc_ht_trig);

	/*
	 * 开始触发器定义。 注意触发器的名称不应带有模式限定，
	 * 但触发器的关系名称可以。
	 */
	initStringInfo(&fc_buf);

	fc_tgname = NameStr(fc_trigrec->tgname);
	appendStringInfo(&fc_buf, "CREATE %sTRIGGER %s ",
					 OidIsValid(fc_trigrec->tgconstraint) ? "CONSTRAINT " : "",
					 quote_identifier(fc_tgname));

	if (TRIGGER_FOR_BEFORE(fc_trigrec->tgtype))
		appendStringInfoString(&fc_buf, "BEFORE");
	else if (TRIGGER_FOR_AFTER(fc_trigrec->tgtype))
		appendStringInfoString(&fc_buf, "AFTER");
	else if (TRIGGER_FOR_INSTEAD(fc_trigrec->tgtype))
		appendStringInfoString(&fc_buf, "INSTEAD OF");
	else
		elog(ERROR, "unexpected tgtype value: %d", fc_trigrec->tgtype);

	if (TRIGGER_FOR_INSERT(fc_trigrec->tgtype))
	{
		appendStringInfoString(&fc_buf, " INSERT");
		fc_findx++;
	}
	if (TRIGGER_FOR_DELETE(fc_trigrec->tgtype))
	{
		if (fc_findx > 0)
			appendStringInfoString(&fc_buf, " OR DELETE");
		else
			appendStringInfoString(&fc_buf, " DELETE");
		fc_findx++;
	}
	if (TRIGGER_FOR_UPDATE(fc_trigrec->tgtype))
	{
		if (fc_findx > 0)
			appendStringInfoString(&fc_buf, " OR UPDATE");
		else
			appendStringInfoString(&fc_buf, " UPDATE");
		fc_findx++;
		/* tgattr 是第一个可变宽度字段，因此可以直接访问 */
		if (fc_trigrec->tgattr.dim1 > 0)
		{
			int			fc_i;

			appendStringInfoString(&fc_buf, " OF ");
			for (fc_i = 0; fc_i < fc_trigrec->tgattr.dim1; fc_i++)
			{
				char	   *fc_attname;

				if (fc_i > 0)
					appendStringInfoString(&fc_buf, ", ");
				fc_attname = get_attname(fc_trigrec->tgrelid,
									  fc_trigrec->tgattr.values[fc_i], false);
				appendStringInfoString(&fc_buf, quote_identifier(fc_attname));
			}
		}
	}
	if (TRIGGER_FOR_TRUNCATE(fc_trigrec->tgtype))
	{
		if (fc_findx > 0)
			appendStringInfoString(&fc_buf, " OR TRUNCATE");
		else
			appendStringInfoString(&fc_buf, " TRUNCATE");
		fc_findx++;
	}

	/*
	 * 在非美观模式下，始终对目标表名称进行模式限定以确保安全。
	 * 在美观模式下，只有在不可见的情况下才进行模式限定。
	 */
	appendStringInfo(&fc_buf, " ON %s ",
					 fc_pretty ?
					 fc_generate_relation_name(fc_trigrec->tgrelid, NIL) :
					 fc_generate_qualified_relation_name(fc_trigrec->tgrelid));

	if (OidIsValid(fc_trigrec->tgconstraint))
	{
		if (OidIsValid(fc_trigrec->tgconstrrelid))
			appendStringInfo(&fc_buf, "FROM %s ",
							 fc_generate_relation_name(fc_trigrec->tgconstrrelid, NIL));
		if (!fc_trigrec->tgdeferrable)
			appendStringInfoString(&fc_buf, "NOT ");
		appendStringInfoString(&fc_buf, "DEFERRABLE INITIALLY ");
		if (fc_trigrec->tginitdeferred)
			appendStringInfoString(&fc_buf, "DEFERRED ");
		else
			appendStringInfoString(&fc_buf, "IMMEDIATE ");
	}

	fc_value = fastgetattr(fc_ht_trig, Anum_pg_trigger_tgoldtable,
						fc_tgrel->rd_att, &fc_isnull);
	if (!fc_isnull)
		fc_tgoldtable = NameStr(*DatumGetName(fc_value));
	else
		fc_tgoldtable = NULL;
	fc_value = fastgetattr(fc_ht_trig, Anum_pg_trigger_tgnewtable,
						fc_tgrel->rd_att, &fc_isnull);
	if (!fc_isnull)
		fc_tgnewtable = NameStr(*DatumGetName(fc_value));
	else
		fc_tgnewtable = NULL;
	if (fc_tgoldtable != NULL || fc_tgnewtable != NULL)
	{
		appendStringInfoString(&fc_buf, "REFERENCING ");
		if (fc_tgoldtable != NULL)
			appendStringInfo(&fc_buf, "OLD TABLE AS %s ",
							 quote_identifier(fc_tgoldtable));
		if (fc_tgnewtable != NULL)
			appendStringInfo(&fc_buf, "NEW TABLE AS %s ",
							 quote_identifier(fc_tgnewtable));
	}

	if (TRIGGER_FOR_ROW(fc_trigrec->tgtype))
		appendStringInfoString(&fc_buf, "FOR EACH ROW ");
	else
		appendStringInfoString(&fc_buf, "FOR EACH STATEMENT ");

	/* 如果触发器有 WHEN 限定条件，则添加该条件 */
	fc_value = fastgetattr(fc_ht_trig, Anum_pg_trigger_tgqual,
						fc_tgrel->rd_att, &fc_isnull);
	if (!fc_isnull)
	{
		Node	   *fc_qual;
		char		fc_relkind;
		deparse_context fc_context;
		deparse_namespace fc_dpns;
		RangeTblEntry *fc_oldrte;
		RangeTblEntry *fc_newrte;

		appendStringInfoString(&fc_buf, "WHEN (");

		fc_qual = stringToNode(TextDatumGetCString(fc_value));

		fc_relkind = get_rel_relkind(fc_trigrec->tgrelid);

		/* 为关系构建最小的 OLD 和 NEW RTEs */
		fc_oldrte = makeNode(RangeTblEntry);
		fc_oldrte->rtekind = RTE_RELATION;
		fc_oldrte->relid = fc_trigrec->tgrelid;
		fc_oldrte->relkind = fc_relkind;
		fc_oldrte->rellockmode = AccessShareLock;
		fc_oldrte->alias = makeAlias("old", NIL);
		fc_oldrte->eref = fc_oldrte->alias;
		fc_oldrte->lateral = false;
		fc_oldrte->inh = false;
		fc_oldrte->inFromCl = true;

		fc_newrte = makeNode(RangeTblEntry);
		fc_newrte->rtekind = RTE_RELATION;
		fc_newrte->relid = fc_trigrec->tgrelid;
		fc_newrte->relkind = fc_relkind;
		fc_newrte->rellockmode = AccessShareLock;
		fc_newrte->alias = makeAlias("new", NIL);
		fc_newrte->eref = fc_newrte->alias;
		fc_newrte->lateral = false;
		fc_newrte->inh = false;
		fc_newrte->inFromCl = true;

		/* 构建两个元素的 rtable */
		memset(&fc_dpns, 0, sizeof(fc_dpns));
		fc_dpns.rtable = list_make2(fc_oldrte, fc_newrte);
		fc_dpns.subplans = NIL;
		fc_dpns.ctes = NIL;
		fc_dpns.appendrels = NULL;
		set_rtable_names(&fc_dpns, NIL, NULL);
		set_simple_column_names(&fc_dpns);

		/* 设置一个深度为一的命名空间栈的上下文 */
		fc_context.buf = &fc_buf;
		fc_context.namespaces = list_make1(&fc_dpns);
		fc_context.windowClause = NIL;
		fc_context.windowTList = NIL;
		fc_context.varprefix = true;
		fc_context.prettyFlags = GET_PRETTY_FLAGS(fc_pretty);
		fc_context.wrapColumn = WRAP_COLUMN_DEFAULT;
		fc_context.indentLevel = PRETTYINDENT_STD;
		fc_context.special_exprkind = EXPR_KIND_NONE;
		fc_context.appendparents = NULL;

		get_rule_expr(fc_qual, &fc_context, false);

		appendStringInfoString(&fc_buf, ") ");
	}

	appendStringInfo(&fc_buf, "EXECUTE FUNCTION %s(",
					 fc_generate_function_name(fc_trigrec->tgfoid, 0,
											NIL, NULL,
											false, NULL, EXPR_KIND_NONE));

	if (fc_trigrec->tgnargs > 0)
	{
		char	   *fc_p;
		int			fc_i;

		fc_value = fastgetattr(fc_ht_trig, Anum_pg_trigger_tgargs,
							fc_tgrel->rd_att, &fc_isnull);
		if (fc_isnull)
			elog(ERROR, "tgargs is null for trigger %u", fc_trigid);
		fc_p = (char *) VARDATA_ANY(DatumGetByteaPP(fc_value));
		for (fc_i = 0; fc_i < fc_trigrec->tgnargs; fc_i++)
		{
			if (fc_i > 0)
				appendStringInfoString(&fc_buf, ", ");
			fc_simple_quote_literal(&fc_buf, fc_p);
			/* 将 p 推进到 tgargs 中的下一个字符串 */
			while (*fc_p)
				fc_p++;
			fc_p++;
		}
	}

	/* 我们故意不在末尾放置分号 */
	appendStringInfoChar(&fc_buf, ')');

	/* 清理 */
	systable_endscan(fc_tgscan);

	table_close(fc_tgrel, AccessShareLock);

	return fc_buf.data;
}

/* ----------
 * pg_get_indexdef - 获取索引的定义
 *
 * 在扩展版本中，还有一个 colno 参数以及 pretty 布尔值。
 * 如果 colno == 0，我们希望获得完整的索引定义。
 * 如果 colno > 0，我们只想要第 N 个索引键的变量或表达式。
 *
 * 请注意，SQL 函数版本的这些省略了关于索引表空间的任何信息；这是有意为之，因为 pg_dump 希望是这样的。
 * 然而 pg_get_indexdef_string() 包括了索引表空间。
 * ----------
 */
Datum pg_get_indexdef(PG_FUNCTION_ARGS)
{
	Oid			fc_indexrelid = PG_GETARG_OID(0);
	int			fc_prettyFlags;
	char	   *fc_res;

	fc_prettyFlags = PRETTYFLAG_INDENT;

	fc_res = pg_get_indexdef_worker(fc_indexrelid, 0, NULL,
								 false, false,
								 false, false,
								 fc_prettyFlags, true);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}

Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS)
{
	Oid			fc_indexrelid = PG_GETARG_OID(0);
	int32		fc_colno = PG_GETARG_INT32(1);
	bool		fc_pretty = PG_GETARG_BOOL(2);
	int			fc_prettyFlags;
	char	   *fc_res;

	fc_prettyFlags = GET_PRETTY_FLAGS(fc_pretty);

	fc_res = pg_get_indexdef_worker(fc_indexrelid, fc_colno, NULL,
								 fc_colno != 0, false,
								 false, false,
								 fc_prettyFlags, true);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}

/*
 * 用于 ALTER TABLE 的内部版本。
 * 结果中包含表空间子句。
 * 返回一个 palloc 的 C 字符串；没有美化打印。
 */
char * pg_get_indexdef_string(Oid fc_indexrelid)
{
	return pg_get_indexdef_worker(fc_indexrelid, 0, NULL,
								  false, false,
								  true, true,
								  0, false);
}

/* 仅报告键列定义的内部版本 */
char * pg_get_indexdef_columns(Oid fc_indexrelid, bool fc_pretty)
{
	int			fc_prettyFlags;

	fc_prettyFlags = GET_PRETTY_FLAGS(fc_pretty);

	return pg_get_indexdef_worker(fc_indexrelid, 0, NULL,
								  true, true,
								  false, false,
								  fc_prettyFlags, false);
}

/* 内部版本，可扩展以控制其行为的标志 */
char * pg_get_indexdef_columns_extended(Oid fc_indexrelid, bits16 fc_flags)
{
	bool		fc_pretty = ((fc_flags & RULE_INDEXDEF_PRETTY) != 0);
	bool		fc_keys_only = ((fc_flags & RULE_INDEXDEF_KEYS_ONLY) != 0);
	int			fc_prettyFlags;

	fc_prettyFlags = GET_PRETTY_FLAGS(fc_pretty);

	return pg_get_indexdef_worker(fc_indexrelid, 0, NULL,
								  true, fc_keys_only,
								  false, false,
								  fc_prettyFlags, false);
}

/*
 * 内部工作马来反编译索引定义。
 *
 * 现在这也用于排除约束：如果 excludeOps 不是 NULL，则它指向一个排除操作符 OID 的数组。
 */
static char * pg_get_indexdef_worker(Oid fc_indexrelid, int fc_colno,
					   const Oid *fc_excludeOps,
					   bool fc_attrsOnly, bool fc_keysOnly,
					   bool fc_showTblSpc, bool fc_inherits,
					   int fc_prettyFlags, bool fc_missing_ok)
{
	/* 可能稍后想要一个单独的 isConstraint 参数 */
	bool		fc_isConstraint = (fc_excludeOps != NULL);
	HeapTuple	fc_ht_idx;
	HeapTuple	fc_ht_idxrel;
	HeapTuple	fc_ht_am;
	Form_pg_index fc_idxrec;
	Form_pg_class fc_idxrelrec;
	Form_pg_am	fc_amrec;
	IndexAmRoutine *fc_amroutine;
	List	   *fc_indexprs;
	ListCell   *fc_indexpr_item;
	List	   *fc_context;
	Oid			fc_indrelid;
	int			fc_keyno;
	Datum		fc_indcollDatum;
	Datum		fc_indclassDatum;
	Datum		fc_indoptionDatum;
	bool		fc_isnull;
	oidvector  *fc_indcollation;
	oidvector  *fc_indclass;
	int2vector *fc_indoption;
	StringInfoData fc_buf;
	char	   *fc_str;
	char	   *fc_sep;

	/*
	 * 根据索引的 Oid 获取 pg_index 元组
	 */
	fc_ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(fc_indexrelid));
	if (!HeapTupleIsValid(fc_ht_idx))
	{
		if (fc_missing_ok)
			return NULL;
		elog(ERROR, "cache lookup failed for index %u", fc_indexrelid);
	}
	fc_idxrec = (Form_pg_index) GETSTRUCT(fc_ht_idx);

	fc_indrelid = fc_idxrec->indrelid;
	Assert(fc_indexrelid == fc_idxrec->indexrelid);

	/* 必须以困难的方式获取 indcollation、indclass 和 indoption */
	fc_indcollDatum = SysCacheGetAttr(INDEXRELID, fc_ht_idx,
								   Anum_pg_index_indcollation, &fc_isnull);
	Assert(!fc_isnull);
	fc_indcollation = (oidvector *) DatumGetPointer(fc_indcollDatum);

	fc_indclassDatum = SysCacheGetAttr(INDEXRELID, fc_ht_idx,
									Anum_pg_index_indclass, &fc_isnull);
	Assert(!fc_isnull);
	fc_indclass = (oidvector *) DatumGetPointer(fc_indclassDatum);

	fc_indoptionDatum = SysCacheGetAttr(INDEXRELID, fc_ht_idx,
									 Anum_pg_index_indoption, &fc_isnull);
	Assert(!fc_isnull);
	fc_indoption = (int2vector *) DatumGetPointer(fc_indoptionDatum);

	/*
	 * 获取索引关系的 pg_class 元组
	 */
	fc_ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_indexrelid));
	if (!HeapTupleIsValid(fc_ht_idxrel))
		elog(ERROR, "cache lookup failed for relation %u", fc_indexrelid);
	fc_idxrelrec = (Form_pg_class) GETSTRUCT(fc_ht_idxrel);

	/*
	 * 获取索引的访问方法的 pg_am 元组
	 */
	fc_ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(fc_idxrelrec->relam));
	if (!HeapTupleIsValid(fc_ht_am))
		elog(ERROR, "cache lookup failed for access method %u",
			 fc_idxrelrec->relam);
	fc_amrec = (Form_pg_am) GETSTRUCT(fc_ht_am);

	/* 获取索引 AM 的 API 结构 */
	fc_amroutine = GetIndexAmRoutine(fc_amrec->amhandler);

	/*
	 * 获取索引表达式（如果有的话）。 （注意：我们不使用 relcache 版本的表达式和谓词，因为我们希望显示非常量折叠的表达式。）
	 */
	if (!heap_attisnull(fc_ht_idx, Anum_pg_index_indexprs, NULL))
	{
		Datum		fc_exprsDatum;
		bool		fc_isnull;
		char	   *fc_exprsString;

		fc_exprsDatum = SysCacheGetAttr(INDEXRELID, fc_ht_idx,
									 Anum_pg_index_indexprs, &fc_isnull);
		Assert(!fc_isnull);
		fc_exprsString = TextDatumGetCString(fc_exprsDatum);
		fc_indexprs = (List *) stringToNode(fc_exprsString);
		pfree(fc_exprsString);
	}
	else
		fc_indexprs = NIL;

	fc_indexpr_item = list_head(fc_indexprs);

	fc_context = deparse_context_for(get_relation_name(fc_indrelid), fc_indrelid);

	/*
	 * 开始索引定义。 注意索引的名称应永远不带模式限定，但被索引关系的名称可能带有。
	 */
	initStringInfo(&fc_buf);

	if (!fc_attrsOnly)
	{
		if (!fc_isConstraint)
			appendStringInfo(&fc_buf, "CREATE %sINDEX %s ON %s%s USING %s (",
							 fc_idxrec->indisunique ? "UNIQUE " : "",
							 quote_identifier(NameStr(fc_idxrelrec->relname)),
							 fc_idxrelrec->relkind == RELKIND_PARTITIONED_INDEX
							 && !fc_inherits ? "ONLY " : "",
							 (fc_prettyFlags & PRETTYFLAG_SCHEMA) ?
							 fc_generate_relation_name(fc_indrelid, NIL) :
							 fc_generate_qualified_relation_name(fc_indrelid),
							 quote_identifier(NameStr(fc_amrec->amname)));
		else					/* 目前必须是 EXCLUDE 约束 */
			appendStringInfo(&fc_buf, "EXCLUDE USING %s (",
							 quote_identifier(NameStr(fc_amrec->amname)));
	}

	/*
	 * 报告被索引的属性
	 */
	fc_sep = "";
	for (fc_keyno = 0; fc_keyno < fc_idxrec->indnatts; fc_keyno++)
	{
		AttrNumber	fc_attnum = fc_idxrec->indkey.values[fc_keyno];
		Oid			fc_keycoltype;
		Oid			fc_keycolcollation;

		/*
		 * 如果被告知，忽略非关键属性。
		 */
		if (fc_keysOnly && fc_keyno >= fc_idxrec->indnkeyatts)
			break;

		/* 否则，打印 INCLUDE 以区分关键属性和非关键属性。 */
		if (!fc_colno && fc_keyno == fc_idxrec->indnkeyatts)
		{
			appendStringInfoString(&fc_buf, ") INCLUDE (");
			fc_sep = "";
		}

		if (!fc_colno)
			appendStringInfoString(&fc_buf, fc_sep);
		fc_sep = ", ";

		if (fc_attnum != 0)
		{
			/* 简单索引列 */
			char	   *fc_attname;
			int32		fc_keycoltypmod;

			fc_attname = get_attname(fc_indrelid, fc_attnum, false);
			if (!fc_colno || fc_colno == fc_keyno + 1)
				appendStringInfoString(&fc_buf, quote_identifier(fc_attname));
			get_atttypetypmodcoll(fc_indrelid, fc_attnum,
								  &fc_keycoltype, &fc_keycoltypmod,
								  &fc_keycolcollation);
		}
		else
		{
			/* 表达式索引 */
			Node	   *fc_indexkey;

			if (fc_indexpr_item == NULL)
				elog(ERROR, "too few entries in indexprs list");
			fc_indexkey = (Node *) lfirst(fc_indexpr_item);
			fc_indexpr_item = lnext(fc_indexprs, fc_indexpr_item);
			/* Deparse */
			fc_str = fc_deparse_expression_pretty(fc_indexkey, fc_context, false, false,
											fc_prettyFlags, 0);
			if (!fc_colno || fc_colno == fc_keyno + 1)
			{
				/* 如果不是裸函数调用，则需要括号 */
				if (fc_looks_like_function(fc_indexkey))
					appendStringInfoString(&fc_buf, fc_str);
				else
					appendStringInfo(&fc_buf, "(%s)", fc_str);
			}
			fc_keycoltype = exprType(fc_indexkey);
			fc_keycolcollation = exprCollation(fc_indexkey);
		}

		/* 为（选定的）关键列打印额外装饰 */
		if (!fc_attrsOnly && fc_keyno < fc_idxrec->indnkeyatts &&
			(!fc_colno || fc_colno == fc_keyno + 1))
		{
			int16		fc_opt = fc_indoption->values[fc_keyno];
			Oid			fc_indcoll = fc_indcollation->values[fc_keyno];
			Datum		fc_attoptions = get_attoptions(fc_indexrelid, fc_keyno + 1);
			bool		fc_has_options = fc_attoptions != (Datum) 0;

			/* 如果不是默认列，则添加排序规则 */
			if (OidIsValid(fc_indcoll) && fc_indcoll != fc_keycolcollation)
				appendStringInfo(&fc_buf, " COLLATE %s",
								 generate_collation_name((fc_indcoll)));

			/* 如果不是默认，则添加操作符类名称 */
			get_opclass_name(fc_indclass->values[fc_keyno],
							 fc_has_options ? InvalidOid : fc_keycoltype, &fc_buf);

			if (fc_has_options)
			{
				appendStringInfoString(&fc_buf, " (");
				get_reloptions(&fc_buf, fc_attoptions);
				appendStringInfoChar(&fc_buf, ')');
			}

			/* 如果相关，则添加选项 */
			if (fc_amroutine->amcanorder)
			{
				/* 如果支持排序顺序，报告 DESC 和 NULLS 选项 */
				if (fc_opt & INDOPTION_DESC)
				{
					appendStringInfoString(&fc_buf, " DESC");
					/* 在这种情况下，NULLS FIRST 是默认值 */
					if (!(fc_opt & INDOPTION_NULLS_FIRST))
						appendStringInfoString(&fc_buf, " NULLS LAST");
				}
				else
				{
					if (fc_opt & INDOPTION_NULLS_FIRST)
						appendStringInfoString(&fc_buf, " NULLS FIRST");
				}
			}

			/* 如果相关，则添加排除操作符 */
			if (fc_excludeOps != NULL)
				appendStringInfo(&fc_buf, " WITH %s",
								 fc_generate_operator_name(fc_excludeOps[fc_keyno],
														fc_keycoltype,
														fc_keycoltype));
		}
	}

	if (!fc_attrsOnly)
	{
		appendStringInfoChar(&fc_buf, ')');

		if (fc_idxrec->indnullsnotdistinct)
			appendStringInfo(&fc_buf, " NULLS NOT DISTINCT");

		/*
		 * 如果有选项，附加 "WITH (options)"
		 */
		fc_str = fc_flatten_reloptions(fc_indexrelid);
		if (fc_str)
		{
			appendStringInfo(&fc_buf, " WITH (%s)", fc_str);
			pfree(fc_str);
		}

		/*
		 * 打印表空间，但仅在请求时
		 */
		if (fc_showTblSpc)
		{
			Oid			fc_tblspc;

			fc_tblspc = get_rel_tablespace(fc_indexrelid);
			if (OidIsValid(fc_tblspc))
			{
				if (fc_isConstraint)
					appendStringInfoString(&fc_buf, " USING INDEX");
				appendStringInfo(&fc_buf, " TABLESPACE %s",
								 quote_identifier(get_tablespace_name(fc_tblspc)));
			}
		}

		/*
		 * 如果这是部分索引，反编译并附加谓词
		 */
		if (!heap_attisnull(fc_ht_idx, Anum_pg_index_indpred, NULL))
		{
			Node	   *fc_node;
			Datum		fc_predDatum;
			bool		fc_isnull;
			char	   *fc_predString;

			/* 将文本字符串转换为节点树 */
			fc_predDatum = SysCacheGetAttr(INDEXRELID, fc_ht_idx,
										Anum_pg_index_indpred, &fc_isnull);
			Assert(!fc_isnull);
			fc_predString = TextDatumGetCString(fc_predDatum);
			fc_node = (Node *) stringToNode(fc_predString);
			pfree(fc_predString);

			/* Deparse */
			fc_str = fc_deparse_expression_pretty(fc_node, fc_context, false, false,
											fc_prettyFlags, 0);
			if (fc_isConstraint)
				appendStringInfo(&fc_buf, " WHERE (%s)", fc_str);
			else
				appendStringInfo(&fc_buf, " WHERE %s", fc_str);
		}
	}

	/* 清理 */
	ReleaseSysCache(fc_ht_idx);
	ReleaseSysCache(fc_ht_idxrel);
	ReleaseSysCache(fc_ht_am);

	return fc_buf.data;
}

/* ----------
 * pg_get_querydef
 *
 * 公共入口点以反解析一个查询解析树。
 * 美观标志由 GET_PRETTY_FLAGS(pretty) 确定。
 *
 * 结果是一个 palloc'd C 字符串。
 * ----------
 */
char * pg_get_querydef(Query *fc_query, bool fc_pretty)
{
	StringInfoData fc_buf;
	int			fc_prettyFlags;

	fc_prettyFlags = GET_PRETTY_FLAGS(fc_pretty);

	initStringInfo(&fc_buf);

	get_query_def(fc_query, &fc_buf, NIL, NULL, true,
				  fc_prettyFlags, WRAP_COLUMN_DEFAULT, 0);

	return fc_buf.data;
}

/*
 * pg_get_statisticsobjdef
 *		获取扩展统计对象的定义
 */
Datum pg_get_statisticsobjdef(PG_FUNCTION_ARGS)
{
	Oid			fc_statextid = PG_GETARG_OID(0);
	char	   *fc_res;

	fc_res = pg_get_statisticsobj_worker(fc_statextid, false, true);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}

/*
 * 用于 ALTER TABLE 的内部版本。
 * 结果中包含表空间子句。
 * 返回一个 palloc 的 C 字符串；没有美化打印。
 */
char * pg_get_statisticsobjdef_string(Oid fc_statextid)
{
	return pg_get_statisticsobj_worker(fc_statextid, false, false);
}

/*
 * pg_get_statisticsobjdef_columns
 *		获取扩展统计对象的列和表达式
 */
Datum pg_get_statisticsobjdef_columns(PG_FUNCTION_ARGS)
{
	Oid			fc_statextid = PG_GETARG_OID(0);
	char	   *fc_res;

	fc_res = pg_get_statisticsobj_worker(fc_statextid, true, true);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}

/*
 * 反编译扩展统计对象的内部工作马。
 */
static char * pg_get_statisticsobj_worker(Oid fc_statextid, bool fc_columns_only, bool fc_missing_ok)
{
	Form_pg_statistic_ext fc_statextrec;
	HeapTuple	fc_statexttup;
	StringInfoData fc_buf;
	int			fc_colno;
	char	   *fc_nsp;
	ArrayType  *fc_arr;
	char	   *fc_enabled;
	Datum		fc_datum;
	bool		fc_isnull;
	bool		fc_ndistinct_enabled;
	bool		fc_dependencies_enabled;
	bool		fc_mcv_enabled;
	int			fc_i;
	List	   *fc_context;
	ListCell   *fc_lc;
	List	   *fc_exprs = NIL;
	bool		fc_has_exprs;
	int			fc_ncolumns;

	fc_statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(fc_statextid));

	if (!HeapTupleIsValid(fc_statexttup))
	{
		if (fc_missing_ok)
			return NULL;
		elog(ERROR, "cache lookup failed for statistics object %u", fc_statextid);
	}

	/* 是否具有统计表达式？ */
	fc_has_exprs = !heap_attisnull(fc_statexttup, Anum_pg_statistic_ext_stxexprs, NULL);

	fc_statextrec = (Form_pg_statistic_ext) GETSTRUCT(fc_statexttup);

	/*
	 * 获取统计表达式（如果有的话）。 （注意：我们不使用 relcache 版本的表达式，因为我们希望显示非常量折叠的表达式。）
	 */
	if (fc_has_exprs)
	{
		Datum		fc_exprsDatum;
		bool		fc_isnull;
		char	   *fc_exprsString;

		fc_exprsDatum = SysCacheGetAttr(STATEXTOID, fc_statexttup,
									 Anum_pg_statistic_ext_stxexprs, &fc_isnull);
		Assert(!fc_isnull);
		fc_exprsString = TextDatumGetCString(fc_exprsDatum);
		fc_exprs = (List *) stringToNode(fc_exprsString);
		pfree(fc_exprsString);
	}
	else
		fc_exprs = NIL;

	/* 计算列（属性和表达式）的数量 */
	fc_ncolumns = fc_statextrec->stxkeys.dim1 + list_length(fc_exprs);

	initStringInfo(&fc_buf);

	if (!fc_columns_only)
	{
		fc_nsp = get_namespace_name_or_temp(fc_statextrec->stxnamespace);
		appendStringInfo(&fc_buf, "CREATE STATISTICS %s",
						 quote_qualified_identifier(fc_nsp,
													NameStr(fc_statextrec->stxname)));

		/*
		 * 解码 stxkind 列，以便我们知道应该打印哪些统计类型。
		 */
		fc_datum = SysCacheGetAttr(STATEXTOID, fc_statexttup,
								Anum_pg_statistic_ext_stxkind, &fc_isnull);
		Assert(!fc_isnull);
		fc_arr = DatumGetArrayTypeP(fc_datum);
		if (ARR_NDIM(fc_arr) != 1 ||
			ARR_HASNULL(fc_arr) ||
			ARR_ELEMTYPE(fc_arr) != CHAROID)
			elog(ERROR, "stxkind is not a 1-D char array");
		fc_enabled = (char *) ARR_DATA_PTR(fc_arr);

		fc_ndistinct_enabled = false;
		fc_dependencies_enabled = false;
		fc_mcv_enabled = false;

		for (fc_i = 0; fc_i < ARR_DIMS(fc_arr)[0]; fc_i++)
		{
			if (fc_enabled[fc_i] == STATS_EXT_NDISTINCT)
				fc_ndistinct_enabled = true;
			else if (fc_enabled[fc_i] == STATS_EXT_DEPENDENCIES)
				fc_dependencies_enabled = true;
			else if (fc_enabled[fc_i] == STATS_EXT_MCV)
				fc_mcv_enabled = true;

			/* 忽略 STATS_EXT_EXPRESSIONS（它是自动生成的） */
		}

		/*
		 * 如果任何选项被禁用，那么我们需要附加类型子句以显示哪些选项被启用。 我们特意在所有选项都启用时省略类型子句，因此 pg_dump/pg_restore 将在更新的 postgres 版本中创建所有统计类型，如果统计信息在原始版本中启用了所有选项。
		 *
		 * 但如果统计信息仅在单个列上定义，则必须是表达式统计。 在这种情况下，我们不需要指定种类。
		 */
		if ((!fc_ndistinct_enabled || !fc_dependencies_enabled || !fc_mcv_enabled) &&
			(fc_ncolumns > 1))
		{
			bool		fc_gotone = false;

			appendStringInfoString(&fc_buf, " (");

			if (fc_ndistinct_enabled)
			{
				appendStringInfoString(&fc_buf, "ndistinct");
				fc_gotone = true;
			}

			if (fc_dependencies_enabled)
			{
				appendStringInfo(&fc_buf, "%sdependencies", fc_gotone ? ", " : "");
				fc_gotone = true;
			}

			if (fc_mcv_enabled)
				appendStringInfo(&fc_buf, "%smcv", fc_gotone ? ", " : "");

			appendStringInfoChar(&fc_buf, ')');
		}

		appendStringInfoString(&fc_buf, " ON ");
	}

	/* 解码简单的列引用 */
	for (fc_colno = 0; fc_colno < fc_statextrec->stxkeys.dim1; fc_colno++)
	{
		AttrNumber	fc_attnum = fc_statextrec->stxkeys.values[fc_colno];
		char	   *fc_attname;

		if (fc_colno > 0)
			appendStringInfoString(&fc_buf, ", ");

		fc_attname = get_attname(fc_statextrec->stxrelid, fc_attnum, false);

		appendStringInfoString(&fc_buf, quote_identifier(fc_attname));
	}

	fc_context = deparse_context_for(get_relation_name(fc_statextrec->stxrelid),
								  fc_statextrec->stxrelid);

	foreach(fc_lc, fc_exprs)
	{
		Node	   *fc_expr = (Node *) lfirst(fc_lc);
		char	   *fc_str;
		int			fc_prettyFlags = PRETTYFLAG_PAREN;

		fc_str = fc_deparse_expression_pretty(fc_expr, fc_context, false, false,
										fc_prettyFlags, 0);

		if (fc_colno > 0)
			appendStringInfoString(&fc_buf, ", ");

		/* 如果不是裸函数调用，则需要括号 */
		if (fc_looks_like_function(fc_expr))
			appendStringInfoString(&fc_buf, fc_str);
		else
			appendStringInfo(&fc_buf, "(%s)", fc_str);

		fc_colno++;
	}

	if (!fc_columns_only)
		appendStringInfo(&fc_buf, " FROM %s",
						 fc_generate_relation_name(fc_statextrec->stxrelid, NIL));

	ReleaseSysCache(fc_statexttup);

	return fc_buf.data;
}

/*
 * 生成统计对象的表达式文本数组。
 */
Datum pg_get_statisticsobjdef_expressions(PG_FUNCTION_ARGS)
{
	Oid			fc_statextid = PG_GETARG_OID(0);
	Form_pg_statistic_ext fc_statextrec;
	HeapTuple	fc_statexttup;
	Datum		fc_datum;
	bool		fc_isnull;
	List	   *fc_context;
	ListCell   *fc_lc;
	List	   *fc_exprs = NIL;
	bool		fc_has_exprs;
	char	   *fc_tmp;
	ArrayBuildState *fc_astate = NULL;

	fc_statexttup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(fc_statextid));

	if (!HeapTupleIsValid(fc_statexttup))
		PG_RETURN_NULL();

	/* 统计对象有表达式吗？ */
	fc_has_exprs = !heap_attisnull(fc_statexttup, Anum_pg_statistic_ext_stxexprs, NULL);

	/* 没有表达式吗？我们完成了 */
	if (!fc_has_exprs)
	{
		ReleaseSysCache(fc_statexttup);
		PG_RETURN_NULL();
	}

	fc_statextrec = (Form_pg_statistic_ext) GETSTRUCT(fc_statexttup);

	/*
	 * 获取统计表达式，并将其反解析为文本值。
	 */
	fc_datum = SysCacheGetAttr(STATEXTOID, fc_statexttup,
							Anum_pg_statistic_ext_stxexprs, &fc_isnull);

	Assert(!fc_isnull);
	fc_tmp = TextDatumGetCString(fc_datum);
	fc_exprs = (List *) stringToNode(fc_tmp);
	pfree(fc_tmp);

	fc_context = deparse_context_for(get_relation_name(fc_statextrec->stxrelid),
								  fc_statextrec->stxrelid);

	foreach(fc_lc, fc_exprs)
	{
		Node	   *fc_expr = (Node *) lfirst(fc_lc);
		char	   *fc_str;
		int			fc_prettyFlags = PRETTYFLAG_INDENT;

		fc_str = fc_deparse_expression_pretty(fc_expr, fc_context, false, false,
										fc_prettyFlags, 0);

		fc_astate = accumArrayResult(fc_astate,
								  PointerGetDatum(cstring_to_text(fc_str)),
								  false,
								  TEXTOID,
								  CurrentMemoryContext);
	}

	ReleaseSysCache(fc_statexttup);

	PG_RETURN_DATUM(makeArrayResult(fc_astate, CurrentMemoryContext));
}

/*
 * pg_get_partkeydef
 *
 * 返回分区键规范，即以下内容：
 *
 * PARTITION BY { RANGE | LIST | HASH } (column opt_collation opt_opclass [, ...])
 */
Datum pg_get_partkeydef(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	char	   *fc_res;

	fc_res = pg_get_partkeydef_worker(fc_relid, PRETTYFLAG_INDENT, false, true);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}

/* 内部版本，仅报告列定义 */
char * pg_get_partkeydef_columns(Oid fc_relid, bool fc_pretty)
{
	int			fc_prettyFlags;

	fc_prettyFlags = GET_PRETTY_FLAGS(fc_pretty);

	return pg_get_partkeydef_worker(fc_relid, fc_prettyFlags, true, false);
}

/*
 * 内部工作函数，用于反编译分区键定义。
 */
static char * pg_get_partkeydef_worker(Oid fc_relid, int fc_prettyFlags,
						 bool fc_attrsOnly, bool fc_missing_ok)
{
	Form_pg_partitioned_table fc_form;
	HeapTuple	fc_tuple;
	oidvector  *fc_partclass;
	oidvector  *fc_partcollation;
	List	   *fc_partexprs;
	ListCell   *fc_partexpr_item;
	List	   *fc_context;
	Datum		fc_datum;
	bool		fc_isnull;
	StringInfoData fc_buf;
	int			fc_keyno;
	char	   *fc_str;
	char	   *fc_sep;

	fc_tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tuple))
	{
		if (fc_missing_ok)
			return NULL;
		elog(ERROR, "cache lookup failed for partition key of %u", fc_relid);
	}

	fc_form = (Form_pg_partitioned_table) GETSTRUCT(fc_tuple);

	Assert(fc_form->partrelid == fc_relid);

	/* 必须以困难的方式获取 partclass 和 partcollation */
	fc_datum = SysCacheGetAttr(PARTRELID, fc_tuple,
							Anum_pg_partitioned_table_partclass, &fc_isnull);
	Assert(!fc_isnull);
	fc_partclass = (oidvector *) DatumGetPointer(fc_datum);

	fc_datum = SysCacheGetAttr(PARTRELID, fc_tuple,
							Anum_pg_partitioned_table_partcollation, &fc_isnull);
	Assert(!fc_isnull);
	fc_partcollation = (oidvector *) DatumGetPointer(fc_datum);


	/*
	 * 获取表达式（如果有）。 （注意：我们不使用表达式的 relcache 版本，因为我们希望显示非常量折叠的表达式。）
	 */
	if (!heap_attisnull(fc_tuple, Anum_pg_partitioned_table_partexprs, NULL))
	{
		Datum		fc_exprsDatum;
		bool		fc_isnull;
		char	   *fc_exprsString;

		fc_exprsDatum = SysCacheGetAttr(PARTRELID, fc_tuple,
									 Anum_pg_partitioned_table_partexprs, &fc_isnull);
		Assert(!fc_isnull);
		fc_exprsString = TextDatumGetCString(fc_exprsDatum);
		fc_partexprs = (List *) stringToNode(fc_exprsString);

		if (!IsA(fc_partexprs, List))
			elog(ERROR, "unexpected node type found in partexprs: %d",
				 (int) nodeTag(fc_partexprs));

		pfree(fc_exprsString);
	}
	else
		fc_partexprs = NIL;

	fc_partexpr_item = list_head(fc_partexprs);
	fc_context = deparse_context_for(get_relation_name(fc_relid), fc_relid);

	initStringInfo(&fc_buf);

	switch (fc_form->partstrat)
	{
		case PARTITION_STRATEGY_HASH:
			if (!fc_attrsOnly)
				appendStringInfoString(&fc_buf, "HASH");
			break;
		case PARTITION_STRATEGY_LIST:
			if (!fc_attrsOnly)
				appendStringInfoString(&fc_buf, "LIST");
			break;
		case PARTITION_STRATEGY_RANGE:
			if (!fc_attrsOnly)
				appendStringInfoString(&fc_buf, "RANGE");
			break;
		default:
			elog(ERROR, "unexpected partition strategy: %d",
				 (int) fc_form->partstrat);
	}

	if (!fc_attrsOnly)
		appendStringInfoString(&fc_buf, " (");
	fc_sep = "";
	for (fc_keyno = 0; fc_keyno < fc_form->partnatts; fc_keyno++)
	{
		AttrNumber	fc_attnum = fc_form->partattrs.values[fc_keyno];
		Oid			fc_keycoltype;
		Oid			fc_keycolcollation;
		Oid			fc_partcoll;

		appendStringInfoString(&fc_buf, fc_sep);
		fc_sep = ", ";
		if (fc_attnum != 0)
		{
			/* 简单属性引用 */
			char	   *fc_attname;
			int32		fc_keycoltypmod;

			fc_attname = get_attname(fc_relid, fc_attnum, false);
			appendStringInfoString(&fc_buf, quote_identifier(fc_attname));
			get_atttypetypmodcoll(fc_relid, fc_attnum,
								  &fc_keycoltype, &fc_keycoltypmod,
								  &fc_keycolcollation);
		}
		else
		{
			/* 表达式 */
			Node	   *fc_partkey;

			if (fc_partexpr_item == NULL)
				elog(ERROR, "too few entries in partexprs list");
			fc_partkey = (Node *) lfirst(fc_partexpr_item);
			fc_partexpr_item = lnext(fc_partexprs, fc_partexpr_item);

			/* Deparse */
			fc_str = fc_deparse_expression_pretty(fc_partkey, fc_context, false, false,
											fc_prettyFlags, 0);
			/* 如果不是裸函数调用，则需要括号 */
			if (fc_looks_like_function(fc_partkey))
				appendStringInfoString(&fc_buf, fc_str);
			else
				appendStringInfo(&fc_buf, "(%s)", fc_str);

			fc_keycoltype = exprType(fc_partkey);
			fc_keycolcollation = exprCollation(fc_partkey);
		}

		/* 如果不是默认列，则添加排序规则 */
		fc_partcoll = fc_partcollation->values[fc_keyno];
		if (!fc_attrsOnly && OidIsValid(fc_partcoll) && fc_partcoll != fc_keycolcollation)
			appendStringInfo(&fc_buf, " COLLATE %s",
							 generate_collation_name((fc_partcoll)));

		/* 如果不是默认，则添加操作符类名称 */
		if (!fc_attrsOnly)
			get_opclass_name(fc_partclass->values[fc_keyno], fc_keycoltype, &fc_buf);
	}

	if (!fc_attrsOnly)
		appendStringInfoChar(&fc_buf, ')');

	/* 清理 */
	ReleaseSysCache(fc_tuple);

	return fc_buf.data;
}

/*
 * pg_get_partition_constraintdef
 *
 * 返回输入关系的分区约束表达式作为字符串
 */
Datum pg_get_partition_constraintdef(PG_FUNCTION_ARGS)
{
	Oid			fc_relationId = PG_GETARG_OID(0);
	Expr	   *fc_constr_expr;
	int			fc_prettyFlags;
	List	   *fc_context;
	char	   *fc_consrc;

	fc_constr_expr = get_partition_qual_relid(fc_relationId);

	/* 如果没有分区约束则快速退出 */
	if (fc_constr_expr == NULL)
		PG_RETURN_NULL();

	/*
	 * 反解析并返回约束表达式。
	 */
	fc_prettyFlags = PRETTYFLAG_INDENT;
	fc_context = deparse_context_for(get_relation_name(fc_relationId), fc_relationId);
	fc_consrc = fc_deparse_expression_pretty((Node *) fc_constr_expr, fc_context, false,
									   false, fc_prettyFlags, 0);

	PG_RETURN_TEXT_P(fc_string_to_text(fc_consrc));
}

/*
 * pg_get_partconstrdef_string
 *
 * 返回输入关系的分区约束，作为 C 字符串，带有给定别名。没有美化打印。
 */
char * pg_get_partconstrdef_string(Oid fc_partitionId, char *fc_aliasname)
{
	Expr	   *fc_constr_expr;
	List	   *fc_context;

	fc_constr_expr = get_partition_qual_relid(fc_partitionId);
	fc_context = deparse_context_for(fc_aliasname, fc_partitionId);

	return deparse_expression((Node *) fc_constr_expr, fc_context, true, false);
}

/*
 * pg_get_constraintdef
 *
 * 返回约束的定义，即在 "ALTER TABLE ... ADD CONSTRAINT <constraintname>" 后需要出现的所有内容。
 */
Datum pg_get_constraintdef(PG_FUNCTION_ARGS)
{
	Oid			fc_constraintId = PG_GETARG_OID(0);
	int			fc_prettyFlags;
	char	   *fc_res;

	fc_prettyFlags = PRETTYFLAG_INDENT;

	fc_res = pg_get_constraintdef_worker(fc_constraintId, false, fc_prettyFlags, true);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}

Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS)
{
	Oid			fc_constraintId = PG_GETARG_OID(0);
	bool		fc_pretty = PG_GETARG_BOOL(1);
	int			fc_prettyFlags;
	char	   *fc_res;

	fc_prettyFlags = GET_PRETTY_FLAGS(fc_pretty);

	fc_res = pg_get_constraintdef_worker(fc_constraintId, false, fc_prettyFlags, true);

	if (fc_res == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_string_to_text(fc_res));
}

/*
 * 内部版本，返回完整的 ALTER TABLE ... ADD CONSTRAINT 命令
 */
char * pg_get_constraintdef_command(Oid fc_constraintId)
{
	return pg_get_constraintdef_worker(fc_constraintId, true, 0, false);
}

/*
 * 从 9.4 开始，我们现在为此使用 MVCC 快照。
 */
static char * pg_get_constraintdef_worker(Oid fc_constraintId, bool fc_fullCommand,
							int fc_prettyFlags, bool fc_missing_ok)
{
	HeapTuple	fc_tup;
	Form_pg_constraint fc_conForm;
	StringInfoData fc_buf;
	SysScanDesc fc_scandesc;
	ScanKeyData fc_scankey[1];
	Snapshot	fc_snapshot = RegisterSnapshot(GetTransactionSnapshot());
	Relation	fc_relation = table_open(ConstraintRelationId, AccessShareLock);

	ScanKeyInit(&fc_scankey[0],
				Anum_pg_constraint_oid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_constraintId));

	fc_scandesc = systable_beginscan(fc_relation,
								  ConstraintOidIndexId,
								  true,
								  fc_snapshot,
								  1,
								  fc_scankey);

	/*
	 * 我们稍后将使用 SysCacheGetAttr() 的元组，就像我们通过 SearchSysCache 获得它一样，这样工作得很好。
	 */
	fc_tup = systable_getnext(fc_scandesc);

	UnregisterSnapshot(fc_snapshot);

	if (!HeapTupleIsValid(fc_tup))
	{
		if (fc_missing_ok)
		{
			systable_endscan(fc_scandesc);
			table_close(fc_relation, AccessShareLock);
			return NULL;
		}
		elog(ERROR, "could not find tuple for constraint %u", fc_constraintId);
	}

	fc_conForm = (Form_pg_constraint) GETSTRUCT(fc_tup);

	initStringInfo(&fc_buf);

	if (fc_fullCommand)
	{
		if (OidIsValid(fc_conForm->conrelid))
		{
			/*
			 * 目前，调用者希望对 CHECK 约束使用 ALTER TABLE（不带 ONLY），而其他类型的约束无论如何都不会继承，所以无论我们是否说 ONLY 都无所谓。总有一天，我们可能需要让调用者指定是否在命令中添加 ONLY。
			 */
			appendStringInfo(&fc_buf, "ALTER TABLE %s ADD CONSTRAINT %s ",
							 fc_generate_qualified_relation_name(fc_conForm->conrelid),
							 quote_identifier(NameStr(fc_conForm->conname)));
		}
		else
		{
			/* 必须是域约束 */
			Assert(OidIsValid(fc_conForm->contypid));
			appendStringInfo(&fc_buf, "ALTER DOMAIN %s ADD CONSTRAINT %s ",
							 fc_generate_qualified_type_name(fc_conForm->contypid),
							 quote_identifier(NameStr(fc_conForm->conname)));
		}
	}

	switch (fc_conForm->contype)
	{
		case CONSTRAINT_FOREIGN:
			{
				Datum		fc_val;
				bool		fc_isnull;
				const char *fc_string;

				/* 开始约束定义 */
				appendStringInfoString(&fc_buf, "FOREIGN KEY (");

				/* 获取并构建引用列列表 */
				fc_val = SysCacheGetAttr(CONSTROID, fc_tup,
									  Anum_pg_constraint_conkey, &fc_isnull);
				if (fc_isnull)
					elog(ERROR, "null conkey for constraint %u",
						 fc_constraintId);

				fc_decompile_column_index_array(fc_val, fc_conForm->conrelid, &fc_buf);

				/* 添加外部关系名称 */
				appendStringInfo(&fc_buf, ") REFERENCES %s(",
								 fc_generate_relation_name(fc_conForm->confrelid,
														NIL));

				/* 获取并构建被引用列列表 */
				fc_val = SysCacheGetAttr(CONSTROID, fc_tup,
									  Anum_pg_constraint_confkey, &fc_isnull);
				if (fc_isnull)
					elog(ERROR, "null confkey for constraint %u",
						 fc_constraintId);

				fc_decompile_column_index_array(fc_val, fc_conForm->confrelid, &fc_buf);

				appendStringInfoChar(&fc_buf, ')');

				/* 添加匹配类型 */
				switch (fc_conForm->confmatchtype)
				{
					case FKCONSTR_MATCH_FULL:
						fc_string = " MATCH FULL";
						break;
					case FKCONSTR_MATCH_PARTIAL:
						fc_string = " MATCH PARTIAL";
						break;
					case FKCONSTR_MATCH_SIMPLE:
						fc_string = "";
						break;
					default:
						elog(ERROR, "unrecognized confmatchtype: %d",
							 fc_conForm->confmatchtype);
						fc_string = "";	/* 保持编译器安静 */
						break;
				}
				appendStringInfoString(&fc_buf, fc_string);

				/* 如果需要，添加 ON UPDATE 和 ON DELETE 子句 */
				switch (fc_conForm->confupdtype)
				{
					case FKCONSTR_ACTION_NOACTION:
						fc_string = NULL;	/* 抑制默认值 */
						break;
					case FKCONSTR_ACTION_RESTRICT:
						fc_string = "RESTRICT";
						break;
					case FKCONSTR_ACTION_CASCADE:
						fc_string = "CASCADE";
						break;
					case FKCONSTR_ACTION_SETNULL:
						fc_string = "SET NULL";
						break;
					case FKCONSTR_ACTION_SETDEFAULT:
						fc_string = "SET DEFAULT";
						break;
					default:
						elog(ERROR, "unrecognized confupdtype: %d",
							 fc_conForm->confupdtype);
						fc_string = NULL;	/* 保持编译器安静 */
						break;
				}
				if (fc_string)
					appendStringInfo(&fc_buf, " ON UPDATE %s", fc_string);

				switch (fc_conForm->confdeltype)
				{
					case FKCONSTR_ACTION_NOACTION:
						fc_string = NULL;	/* 抑制默认值 */
						break;
					case FKCONSTR_ACTION_RESTRICT:
						fc_string = "RESTRICT";
						break;
					case FKCONSTR_ACTION_CASCADE:
						fc_string = "CASCADE";
						break;
					case FKCONSTR_ACTION_SETNULL:
						fc_string = "SET NULL";
						break;
					case FKCONSTR_ACTION_SETDEFAULT:
						fc_string = "SET DEFAULT";
						break;
					default:
						elog(ERROR, "unrecognized confdeltype: %d",
							 fc_conForm->confdeltype);
						fc_string = NULL;	/* 保持编译器安静 */
						break;
				}
				if (fc_string)
					appendStringInfo(&fc_buf, " ON DELETE %s", fc_string);

				/*
				 * 如果提供了，添加指定的列以设置为 NULL 或默认值。
				 */
				fc_val = SysCacheGetAttr(CONSTROID, fc_tup,
									  Anum_pg_constraint_confdelsetcols, &fc_isnull);
				if (!fc_isnull)
				{
					appendStringInfo(&fc_buf, " (");
					fc_decompile_column_index_array(fc_val, fc_conForm->conrelid, &fc_buf);
					appendStringInfo(&fc_buf, ")");
				}

				break;
			}
		case CONSTRAINT_PRIMARY:
		case CONSTRAINT_UNIQUE:
			{
				Datum		fc_val;
				bool		fc_isnull;
				Oid			fc_indexId;
				int			fc_keyatts;
				HeapTuple	fc_indtup;

				/* 开始约束定义 */
				if (fc_conForm->contype == CONSTRAINT_PRIMARY)
					appendStringInfoString(&fc_buf, "PRIMARY KEY ");
				else
					appendStringInfoString(&fc_buf, "UNIQUE ");

				fc_indexId = fc_conForm->conindid;

				fc_indtup = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(fc_indexId));
				if (!HeapTupleIsValid(fc_indtup))
					elog(ERROR, "cache lookup failed for index %u", fc_indexId);
				if (fc_conForm->contype == CONSTRAINT_UNIQUE &&
					((Form_pg_index) GETSTRUCT(fc_indtup))->indnullsnotdistinct)
					appendStringInfoString(&fc_buf, "NULLS NOT DISTINCT ");

				appendStringInfoString(&fc_buf, "(");

				/* 获取并构建目标列列表 */
				fc_val = SysCacheGetAttr(CONSTROID, fc_tup,
									  Anum_pg_constraint_conkey, &fc_isnull);
				if (fc_isnull)
					elog(ERROR, "null conkey for constraint %u",
						 fc_constraintId);

				fc_keyatts = fc_decompile_column_index_array(fc_val, fc_conForm->conrelid, &fc_buf);

				appendStringInfoChar(&fc_buf, ')');

				/* 构建包含列列表（来自 pg_index.indkeys） */
				fc_val = SysCacheGetAttr(INDEXRELID, fc_indtup,
									  Anum_pg_index_indnatts, &fc_isnull);
				if (fc_isnull)
					elog(ERROR, "null indnatts for index %u", fc_indexId);
				if (DatumGetInt32(fc_val) > fc_keyatts)
				{
					Datum		fc_cols;
					Datum	   *fc_keys;
					int			fc_nKeys;
					int			fc_j;

					appendStringInfoString(&fc_buf, " INCLUDE (");

					fc_cols = SysCacheGetAttr(INDEXRELID, fc_indtup,
										   Anum_pg_index_indkey, &fc_isnull);
					if (fc_isnull)
						elog(ERROR, "null indkey for index %u", fc_indexId);

					deconstruct_array(DatumGetArrayTypeP(fc_cols),
									  INT2OID, 2, true, TYPALIGN_SHORT,
									  &fc_keys, NULL, &fc_nKeys);

					for (fc_j = fc_keyatts; fc_j < fc_nKeys; fc_j++)
					{
						char	   *fc_colName;

						fc_colName = get_attname(fc_conForm->conrelid,
											  DatumGetInt16(fc_keys[fc_j]), false);
						if (fc_j > fc_keyatts)
							appendStringInfoString(&fc_buf, ", ");
						appendStringInfoString(&fc_buf, quote_identifier(fc_colName));
					}

					appendStringInfoChar(&fc_buf, ')');
				}
				ReleaseSysCache(fc_indtup);

				/* XXX 为什么我们只有在 fullCommand 时才打印这些内容？ */
				if (fc_fullCommand && OidIsValid(fc_indexId))
				{
					char	   *fc_options = fc_flatten_reloptions(fc_indexId);
					Oid			fc_tblspc;

					if (fc_options)
					{
						appendStringInfo(&fc_buf, " WITH (%s)", fc_options);
						pfree(fc_options);
					}

					/*
					 * 打印表空间，除非它是数据库默认的。
					 * 这有助于 ALTER TABLE 使用此功能，
					 * 该功能需要这种行为来重建确切的目录
					 * 状态。
					 */
					fc_tblspc = get_rel_tablespace(fc_indexId);
					if (OidIsValid(fc_tblspc))
						appendStringInfo(&fc_buf, " USING INDEX TABLESPACE %s",
										 quote_identifier(get_tablespace_name(fc_tblspc)));
				}

				break;
			}
		case CONSTRAINT_CHECK:
			{
				Datum		fc_val;
				bool		fc_isnull;
				char	   *fc_conbin;
				char	   *fc_consrc;
				Node	   *fc_expr;
				List	   *fc_context;

				/* 以解析树形式获取约束表达式 */
				fc_val = SysCacheGetAttr(CONSTROID, fc_tup,
									  Anum_pg_constraint_conbin, &fc_isnull);
				if (fc_isnull)
					elog(ERROR, "null conbin for constraint %u",
						 fc_constraintId);

				fc_conbin = TextDatumGetCString(fc_val);
				fc_expr = stringToNode(fc_conbin);

				/* 为约束中的 Var 节点设置反解析上下文 */
				if (fc_conForm->conrelid != InvalidOid)
				{
					/* 关系约束 */
					fc_context = deparse_context_for(get_relation_name(fc_conForm->conrelid),
												  fc_conForm->conrelid);
				}
				else
				{
					/* 域约束 --- 不能有 Vars */
					fc_context = NIL;
				}

				fc_consrc = fc_deparse_expression_pretty(fc_expr, fc_context, false, false,
												   fc_prettyFlags, 0);

				/*
				 * 现在发出约束定义，如果必要的话添加 NO INHERIT。
				 *
				 * 有些情况下，约束表达式将完全用括号括起来，外部括号
				 * 不是必需的……但还有其它情况下我们确实需要它们。 
				 * 现在要谨慎些。
				 *
				 * 注意，仅仅检查前导 '(' 和尾随 ')' 
				 * 还不够好，考虑 "(x > 0) AND (y > 0)"。
				 */
				appendStringInfo(&fc_buf, "CHECK (%s)%s",
								 fc_consrc,
								 fc_conForm->connoinherit ? " NO INHERIT" : "");
				break;
			}
		case CONSTRAINT_TRIGGER:

			/*
			 * 创建用户定义的约束触发器没有 ALTER TABLE 语法，
			 * 但打印一些内容似乎总比抛出错误要好；
			 * 如果我们抛出错误，那么此函数就无法安全地应用于
			 * pg_constraint 的所有行。
			 */
			appendStringInfoString(&fc_buf, "TRIGGER");
			break;
		case CONSTRAINT_EXCLUSION:
			{
				Oid			fc_indexOid = fc_conForm->conindid;
				Datum		fc_val;
				bool		fc_isnull;
				Datum	   *fc_elems;
				int			fc_nElems;
				int			fc_i;
				Oid		   *fc_operators;

				/* 从 pg_constraint 元组中提取操作符 OID */
				fc_val = SysCacheGetAttr(CONSTROID, fc_tup,
									  Anum_pg_constraint_conexclop,
									  &fc_isnull);
				if (fc_isnull)
					elog(ERROR, "null conexclop for constraint %u",
						 fc_constraintId);

				deconstruct_array(DatumGetArrayTypeP(fc_val),
								  OIDOID, sizeof(Oid), true, TYPALIGN_INT,
								  &fc_elems, NULL, &fc_nElems);

				fc_operators = (Oid *) palloc(fc_nElems * sizeof(Oid));
				for (fc_i = 0; fc_i < fc_nElems; fc_i++)
					fc_operators[fc_i] = DatumGetObjectId(fc_elems[fc_i]);

				/* pg_get_indexdef_worker 处理其余部分 */
				/* 抑制表空间，因为 pg_dump 希望以这种方式进行 */
				appendStringInfoString(&fc_buf,
									   pg_get_indexdef_worker(fc_indexOid,
															  0,
															  fc_operators,
															  false,
															  false,
															  false,
															  false,
															  fc_prettyFlags,
															  false));
				break;
			}
		default:
			elog(ERROR, "invalid constraint type \"%c\"", fc_conForm->contype);
			break;
	}

	if (fc_conForm->condeferrable)
		appendStringInfoString(&fc_buf, " DEFERRABLE");
	if (fc_conForm->condeferred)
		appendStringInfoString(&fc_buf, " INITIALLY DEFERRED");
	if (!fc_conForm->convalidated)
		appendStringInfoString(&fc_buf, " NOT VALID");

	/* 清理 */
	systable_endscan(fc_scandesc);
	table_close(fc_relation, AccessShareLock);

	return fc_buf.data;
}


/*
 * 将一个 int16[] Datum 转换为指示关系的以逗号分隔的列名列表；
 * 将列表附加到 buf。返回键的数量。
 */
static int fc_decompile_column_index_array(Datum fc_column_index_array, Oid fc_relId,
							 StringInfo fc_buf)
{
	Datum	   *fc_keys;
	int			fc_nKeys;
	int			fc_j;

	/* 从 int16 数组中提取数据 */
	deconstruct_array(DatumGetArrayTypeP(fc_column_index_array),
					  INT2OID, 2, true, TYPALIGN_SHORT,
					  &fc_keys, NULL, &fc_nKeys);

	for (fc_j = 0; fc_j < fc_nKeys; fc_j++)
	{
		char	   *fc_colName;

		fc_colName = get_attname(fc_relId, DatumGetInt16(fc_keys[fc_j]), false);

		if (fc_j == 0)
			appendStringInfoString(fc_buf, quote_identifier(fc_colName));
		else
			appendStringInfo(fc_buf, ", %s", quote_identifier(fc_colName));
	}

	return fc_nKeys;
}


/* ----------
 * pg_get_expr			- 反编译一个表达式树
 *
 * 输入：一个以 nodeToString 形式的表达式树，以及一个关系 OID
 *
 * 输出：反向列出的表达式
 *
 * 当前，表达式只能引用一个单一关系，即由第二个参数指定的关系。
 * 这对于部分索引、列默认表达式等是足够的。我们还支持
 * 无 Var 表达式，对于这些表达式，OID 可以是 InvalidOid。
 *
 * 如果 OID 非零但实际上无效，不要抛出错误，
 * 只需返回 NULL。这有些问题，但这是我们历史上
 * 的做法，并且可以帮助避免在检查刚删除的关系的目录条目时
 * 遇到不必要的失败。
 *
 * 我们希望这个函数能正常工作，或抛出合理干净的错误，
 * 对于可以出现在目录 pg_node_tree 列中的任何节点树。
 * 查询树，例如出现在 pg_rewrite.ev_action 中的那些，
 * 不被支持。包含在多个关系中的表达式也是如此，例如
 * 可以出现在 pg_rewrite.ev_qual 中的关系。
 * ----------
 */
Datum pg_get_expr(PG_FUNCTION_ARGS)
{
	text	   *fc_expr = PG_GETARG_TEXT_PP(0);
	Oid			fc_relid = PG_GETARG_OID(1);
	text	   *fc_result;
	int			fc_prettyFlags;

	fc_prettyFlags = PRETTYFLAG_INDENT;

	fc_result = pg_get_expr_worker(fc_expr, fc_relid, fc_prettyFlags);
	if (fc_result)
		PG_RETURN_TEXT_P(fc_result);
	else
		PG_RETURN_NULL();
}

Datum pg_get_expr_ext(PG_FUNCTION_ARGS)
{
	text	   *fc_expr = PG_GETARG_TEXT_PP(0);
	Oid			fc_relid = PG_GETARG_OID(1);
	bool		fc_pretty = PG_GETARG_BOOL(2);
	text	   *fc_result;
	int			fc_prettyFlags;

	fc_prettyFlags = GET_PRETTY_FLAGS(fc_pretty);

	fc_result = pg_get_expr_worker(fc_expr, fc_relid, fc_prettyFlags);
	if (fc_result)
		PG_RETURN_TEXT_P(fc_result);
	else
		PG_RETURN_NULL();
}

static text * pg_get_expr_worker(text *fc_expr, Oid fc_relid, int fc_prettyFlags)
{
	Node	   *fc_node;
	Node	   *fc_tst;
	Relids		fc_relids;
	List	   *fc_context;
	char	   *fc_exprstr;
	Relation	fc_rel = NULL;
	char	   *fc_str;

	/* 将输入的 pg_node_tree（实际上是 TEXT）对象转换为 C 字符串 */
	fc_exprstr = text_to_cstring(fc_expr);

	/* 将表达式转换为节点树 */
	fc_node = (Node *) stringToNode(fc_exprstr);

	pfree(fc_exprstr);

	/*
	 * 如果输入是查询树而不是表达式树则抛出错误。
	 * 尽管我们可以在这里支持查询，但似乎没有很好的理由
	 * 这么做。在大多数此类目录列中，我们将看到 Query 节点的 List，
	 * 或甚至嵌套的 List，因此在检查之前先深入到非 List 节点。
	 */
	fc_tst = fc_node;
	while (fc_tst && IsA(fc_tst, List))
		fc_tst = linitial((List *) fc_tst);
	if (fc_tst && IsA(fc_tst, Query))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("input is a query, not an expression")));

	/*
	 * 如果表达式包含我们无法反解析的 Vars，则抛出错误。
	 */
	fc_relids = pull_varnos(NULL, fc_node);
	if (OidIsValid(fc_relid))
	{
		if (!bms_is_subset(fc_relids, bms_make_singleton(1)))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("expression contains variables of more than one relation")));
	}
	else
	{
		if (!bms_is_empty(fc_relids))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("expression contains variables")));
	}

	/*
	 * 如果需要，准备反解析上下文。如果我们正在使用 relid 进行反解析，
	 * 需要暂时打开和锁定 rel，以确保它不会在我们
	 * 进行操作时消失。（set_relation_column_names 会锁定它，
	 * 所以这实际上并没有引入任何新行为。）
	 */
	if (OidIsValid(fc_relid))
	{
		fc_rel = try_relation_open(fc_relid, AccessShareLock);
		if (fc_rel == NULL)
			return NULL;
		fc_context = deparse_context_for(RelationGetRelationName(fc_rel), fc_relid);
	}
	else
		fc_context = NIL;

	/* Deparse */
	fc_str = fc_deparse_expression_pretty(fc_node, fc_context, false, false,
									fc_prettyFlags, 0);

	if (fc_rel != NULL)
		relation_close(fc_rel, AccessShareLock);

	return fc_string_to_text(fc_str);
}


/* ----------
 * pg_get_userbyid		- 通过 roleid 获取用户名并
 *				  回退到 'unknown (OID=n)'
 * ----------
 */
Datum pg_get_userbyid(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Name		fc_result;
	HeapTuple	fc_roletup;
	Form_pg_authid fc_role_rec;

	/*
	 * 为结果分配空间
	 */
	fc_result = (Name) palloc(NAMEDATALEN);
	memset(NameStr(*fc_result), 0, NAMEDATALEN);

	/*
	 * 获取 pg_authid 条目并打印结果
	 */
	fc_roletup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(fc_roleid));
	if (HeapTupleIsValid(fc_roletup))
	{
		fc_role_rec = (Form_pg_authid) GETSTRUCT(fc_roletup);
		*fc_result = fc_role_rec->rolname;
		ReleaseSysCache(fc_roletup);
	}
	else
		sprintf(NameStr(*fc_result), "unknown (OID=%u)", fc_roleid);

	PG_RETURN_NAME(fc_result);
}


/*
 * pg_get_serial_sequence
 *		获取身份或序列列所使用的序列名，
 *		以适合传递给 setval、nextval 或 currval 的格式。
 *		第一个参数不作为双引号处理，第二个参数
 *		则是 --- 具体原因见文档。
 */
Datum pg_get_serial_sequence(PG_FUNCTION_ARGS)
{
	text	   *fc_tablename = PG_GETARG_TEXT_PP(0);
	text	   *fc_columnname = PG_GETARG_TEXT_PP(1);
	RangeVar   *fc_tablerv;
	Oid			fc_tableOid;
	char	   *fc_column;
	AttrNumber	fc_attnum;
	Oid			fc_sequenceId = InvalidOid;
	Relation	fc_depRel;
	ScanKeyData fc_key[3];
	SysScanDesc fc_scan;
	HeapTuple	fc_tup;

	/* 查找表名。无法锁定-我们可能没有权限。 */
	fc_tablerv = makeRangeVarFromNameList(textToQualifiedNameList(fc_tablename));
	fc_tableOid = RangeVarGetRelid(fc_tablerv, NoLock, false);

	/* 获取列的编号 */
	fc_column = text_to_cstring(fc_columnname);

	fc_attnum = get_attnum(fc_tableOid, fc_column);
	if (fc_attnum == InvalidAttrNumber)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_COLUMN),
				 errmsg("column \"%s\" of relation \"%s\" does not exist",
						fc_column, fc_tablerv->relname)));

	/* 在依赖表中搜索依赖序列 */
	fc_depRel = table_open(DependRelationId, AccessShareLock);

	ScanKeyInit(&fc_key[0],
				Anum_pg_depend_refclassid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(RelationRelationId));
	ScanKeyInit(&fc_key[1],
				Anum_pg_depend_refobjid,
				BTEqualStrategyNumber, F_OIDEQ,
				ObjectIdGetDatum(fc_tableOid));
	ScanKeyInit(&fc_key[2],
				Anum_pg_depend_refobjsubid,
				BTEqualStrategyNumber, F_INT4EQ,
				Int32GetDatum(fc_attnum));

	fc_scan = systable_beginscan(fc_depRel, DependReferenceIndexId, true,
							  NULL, 3, fc_key);

	while (HeapTupleIsValid(fc_tup = systable_getnext(fc_scan)))
	{
		Form_pg_depend fc_deprec = (Form_pg_depend) GETSTRUCT(fc_tup);

		/*
		 * 查找序列在列上的自动依赖（序列列）或内部依赖
		 * （身份列）。 （我们需要 relkind 测试，因为索引
		 * 也可以对列有自动依赖。）
		 */
		if (fc_deprec->classid == RelationRelationId &&
			fc_deprec->objsubid == 0 &&
			(fc_deprec->deptype == DEPENDENCY_AUTO ||
			 fc_deprec->deptype == DEPENDENCY_INTERNAL) &&
			get_rel_relkind(fc_deprec->objid) == RELKIND_SEQUENCE)
		{
			fc_sequenceId = fc_deprec->objid;
			break;
		}
	}

	systable_endscan(fc_scan);
	table_close(fc_depRel, AccessShareLock);

	if (OidIsValid(fc_sequenceId))
	{
		char	   *fc_result;

		fc_result = fc_generate_qualified_relation_name(fc_sequenceId);

		PG_RETURN_TEXT_P(fc_string_to_text(fc_result));
	}

	PG_RETURN_NULL();
}


/*
 * pg_get_functiondef
 *		返回指定函数的完整 "CREATE OR REPLACE FUNCTION ..."
 *		语句。
 *
 * 注意：如果您更改此函数的输出格式，请小心不要
 * 破坏 psql 在 (\ef 和 \sf) 中识别函数体开始的规则。
 * 也就是说：函数体在以 "AS "、"BEGIN " 或 "RETURN " 
 * 开头的行上开始，且没有前置行看起来像那样。
 */
Datum pg_get_functiondef(PG_FUNCTION_ARGS)
{
	Oid			fc_funcid = PG_GETARG_OID(0);
	StringInfoData fc_buf;
	StringInfoData fc_dq;
	HeapTuple	fc_proctup;
	Form_pg_proc fc_proc;
	bool		fc_isfunction;
	Datum		fc_tmp;
	bool		fc_isnull;
	const char *fc_prosrc;
	const char *fc_name;
	const char *fc_nsp;
	float4		fc_procost;
	int			fc_oldlen;

	initStringInfo(&fc_buf);

	/* 查找函数 */
	fc_proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcid));
	if (!HeapTupleIsValid(fc_proctup))
		PG_RETURN_NULL();

	fc_proc = (Form_pg_proc) GETSTRUCT(fc_proctup);
	fc_name = NameStr(fc_proc->proname);

	if (fc_proc->prokind == PROKIND_AGGREGATE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is an aggregate function", fc_name)));

	fc_isfunction = (fc_proc->prokind != PROKIND_PROCEDURE);

	/*
	 * 我们总是为函数名添加限定，以确保正确的函数被
	 * 替换。
	 */
	fc_nsp = get_namespace_name_or_temp(fc_proc->pronamespace);
	appendStringInfo(&fc_buf, "CREATE OR REPLACE %s %s(",
					 fc_isfunction ? "FUNCTION" : "PROCEDURE",
					 quote_qualified_identifier(fc_nsp, fc_name));
	(void) fc_print_function_arguments(&fc_buf, fc_proctup, false, true);
	appendStringInfoString(&fc_buf, ")\n");
	if (fc_isfunction)
	{
		appendStringInfoString(&fc_buf, " RETURNS ");
		fc_print_function_rettype(&fc_buf, fc_proctup);
		appendStringInfoChar(&fc_buf, '\n');
	}

	fc_print_function_trftypes(&fc_buf, fc_proctup);

	appendStringInfo(&fc_buf, " LANGUAGE %s\n",
					 quote_identifier(get_language_name(fc_proc->prolang, false)));

	/* 在一行上发出一些杂项选项 */
	fc_oldlen = fc_buf.len;

	if (fc_proc->prokind == PROKIND_WINDOW)
		appendStringInfoString(&fc_buf, " WINDOW");
	switch (fc_proc->provolatile)
	{
		case PROVOLATILE_IMMUTABLE:
			appendStringInfoString(&fc_buf, " IMMUTABLE");
			break;
		case PROVOLATILE_STABLE:
			appendStringInfoString(&fc_buf, " STABLE");
			break;
		case PROVOLATILE_VOLATILE:
			break;
	}

	switch (fc_proc->proparallel)
	{
		case PROPARALLEL_SAFE:
			appendStringInfoString(&fc_buf, " PARALLEL SAFE");
			break;
		case PROPARALLEL_RESTRICTED:
			appendStringInfoString(&fc_buf, " PARALLEL RESTRICTED");
			break;
		case PROPARALLEL_UNSAFE:
			break;
	}

	if (fc_proc->proisstrict)
		appendStringInfoString(&fc_buf, " STRICT");
	if (fc_proc->prosecdef)
		appendStringInfoString(&fc_buf, " SECURITY DEFINER");
	if (fc_proc->proleakproof)
		appendStringInfoString(&fc_buf, " LEAKPROOF");

	/* 这些关于默认成本和行的代码应该与 functioncmds.c 匹配 */
	if (fc_proc->prolang == INTERNALlanguageId ||
		fc_proc->prolang == ClanguageId)
		fc_procost = 1;
	else
		fc_procost = 100;
	if (fc_proc->procost != fc_procost)
		appendStringInfo(&fc_buf, " COST %g", fc_proc->procost);

	if (fc_proc->prorows > 0 && fc_proc->prorows != 1000)
		appendStringInfo(&fc_buf, " ROWS %g", fc_proc->prorows);

	if (fc_proc->prosupport)
	{
		Oid			fc_argtypes[1];

		
/*
		 * 如果支持函数的名称无法通过当前搜索路径查找
		 * 到，那么我们应该给它进行限定。
		 */
		fc_argtypes[0] = INTERNALOID;
		appendStringInfo(&fc_buf, " SUPPORT %s",
						 fc_generate_function_name(fc_proc->prosupport, 1,
												NIL, fc_argtypes,
												false, NULL, EXPR_KIND_NONE));
	}

	if (fc_oldlen != fc_buf.len)
		appendStringInfoChar(&fc_buf, '\n');

	/* 逐行输出 proconfig 选项 */
	fc_tmp = SysCacheGetAttr(PROCOID, fc_proctup, Anum_pg_proc_proconfig, &fc_isnull);
	if (!fc_isnull)
	{
		ArrayType  *fc_a = DatumGetArrayTypeP(fc_tmp);
		int			fc_i;

		Assert(ARR_ELEMTYPE(fc_a) == TEXTOID);
		Assert(ARR_NDIM(fc_a) == 1);
		Assert(ARR_LBOUND(fc_a)[0] == 1);

		for (fc_i = 1; fc_i <= ARR_DIMS(fc_a)[0]; fc_i++)
		{
			Datum		fc_d;

			fc_d = array_ref(fc_a, 1, &fc_i,
						  -1 /* tzparser.c在失败时返回NULL，通过GUC_check_errmsg报告 */ ,
						  -1 /* TEXT 的 typlen */ ,
						  false /* TEXT 的 typbyval */ ,
						  TYPALIGN_INT /* TEXT 的 typalign */ ,
						  &fc_isnull);
			if (!fc_isnull)
			{
				char	   *fc_configitem = TextDatumGetCString(fc_d);
				char	   *fc_pos;

				fc_pos = strchr(fc_configitem, '=');
				if (fc_pos == NULL)
					continue;
				*fc_pos++ = '\0';

				appendStringInfo(&fc_buf, " SET %s TO ",
								 quote_identifier(fc_configitem));

				/*
				 * 被标记为 GUC_LIST_QUOTE 的变量在放入
				 * proconfig 数组之前已经通过 flatten_set_variable_args()
				 * 完全引用。然而，由于那里使用的引用规则与 SQL 的
				 * 不完全相同，我们必须将列表值拆分，然后将元素
				 * 引用为字符串字面量。（元素可以原样双引号
				 * 引用，但我们不能直接将它们传递给 SQL 解析器；
				 * 这会导致解析器对零长度或超过 NAMEDATALEN 长度的
				 * 元素做出错误的处理。）
				 *
				 * 没有标记的变量应该作为简单的字符串字面量
				 * 输出。如果变量在 guc.c 中未知，我们将这样做；
				 * 这使得对扩展变量使用 GUC_LIST_QUOTE 变得不安全。
				 */
				if (GetConfigOptionFlags(fc_configitem, true) & GUC_LIST_QUOTE)
				{
					List	   *fc_namelist;
					ListCell   *fc_lc;

					/* 将字符串解析为标识符列表 */
					if (!SplitGUCList(fc_pos, ',', &fc_namelist))
					{
						/* 这实际上不应该失败 */
						elog(ERROR, "invalid list syntax in proconfig item");
					}
					foreach(fc_lc, fc_namelist)
					{
						char	   *fc_curname = (char *) lfirst(fc_lc);

						fc_simple_quote_literal(&fc_buf, fc_curname);
						if (lnext(fc_namelist, fc_lc))
							appendStringInfoString(&fc_buf, ", ");
					}
				}
				else
					fc_simple_quote_literal(&fc_buf, fc_pos);
				appendStringInfoChar(&fc_buf, '\n');
			}
		}
	}

	/* 最后是函数定义 ... */
	(void) SysCacheGetAttr(PROCOID, fc_proctup, Anum_pg_proc_prosqlbody, &fc_isnull);
	if (fc_proc->prolang == SQLlanguageId && !fc_isnull)
	{
		fc_print_function_sqlbody(&fc_buf, fc_proctup);
	}
	else
	{
		appendStringInfoString(&fc_buf, "AS ");

		fc_tmp = SysCacheGetAttr(PROCOID, fc_proctup, Anum_pg_proc_probin, &fc_isnull);
		if (!fc_isnull)
		{
			fc_simple_quote_literal(&fc_buf, TextDatumGetCString(fc_tmp));
			appendStringInfoString(&fc_buf, ", "); /* 假设 prosrc 不为 null */
		}

		fc_tmp = SysCacheGetAttr(PROCOID, fc_proctup, Anum_pg_proc_prosrc, &fc_isnull);
		if (fc_isnull)
			elog(ERROR, "null prosrc");
		fc_prosrc = TextDatumGetCString(fc_tmp);

		/* 
		 * 我们总是使用美元引用。找出合适的分隔符。
		 *
		 * 由于用户可能会编辑函数主体字符串，我们
		 * 不应使用可能轻易产生冲突的短分隔符。因此更倾向于 "$function$"/"$procedure$"，但如有必要则扩大。
		 */
		initStringInfo(&fc_dq);
		appendStringInfoChar(&fc_dq, '$');
		appendStringInfoString(&fc_dq, (fc_isfunction ? "function" : "procedure"));
		while (strstr(fc_prosrc, fc_dq.data) != NULL)
			appendStringInfoChar(&fc_dq, 'x');
		appendStringInfoChar(&fc_dq, '$');

		appendBinaryStringInfo(&fc_buf, fc_dq.data, fc_dq.len);
		appendStringInfoString(&fc_buf, fc_prosrc);
		appendBinaryStringInfo(&fc_buf, fc_dq.data, fc_dq.len);
	}

	appendStringInfoChar(&fc_buf, '\n');

	ReleaseSysCache(fc_proctup);

	PG_RETURN_TEXT_P(fc_string_to_text(fc_buf.data));
}

/* 
 * pg_get_function_arguments
 *		获取函数参数的格式良好的列表。
 *		这就是在 CREATE FUNCTION 的括号之间的所有内容。
 */
Datum pg_get_function_arguments(PG_FUNCTION_ARGS)
{
	Oid			fc_funcid = PG_GETARG_OID(0);
	StringInfoData fc_buf;
	HeapTuple	fc_proctup;

	fc_proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcid));
	if (!HeapTupleIsValid(fc_proctup))
		PG_RETURN_NULL();

	initStringInfo(&fc_buf);

	(void) fc_print_function_arguments(&fc_buf, fc_proctup, false, true);

	ReleaseSysCache(fc_proctup);

	PG_RETURN_TEXT_P(fc_string_to_text(fc_buf.data));
}

/* 
 * pg_get_function_identity_arguments
 *		获取函数参数的格式化列表。
 *		这就是在 ALTER FUNCTION 等的括号之间的所有内容。特别是，不打印默认值。
 */
Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS)
{
	Oid			fc_funcid = PG_GETARG_OID(0);
	StringInfoData fc_buf;
	HeapTuple	fc_proctup;

	fc_proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcid));
	if (!HeapTupleIsValid(fc_proctup))
		PG_RETURN_NULL();

	initStringInfo(&fc_buf);

	(void) fc_print_function_arguments(&fc_buf, fc_proctup, false, false);

	ReleaseSysCache(fc_proctup);

	PG_RETURN_TEXT_P(fc_string_to_text(fc_buf.data));
}

/* 
 * pg_get_function_result
 *		获取函数返回类型的格式良好的版本。
 *		这就是在 CREATE FUNCTION 中 RETURNS 之后会出现的内容。
 */
Datum pg_get_function_result(PG_FUNCTION_ARGS)
{
	Oid			fc_funcid = PG_GETARG_OID(0);
	StringInfoData fc_buf;
	HeapTuple	fc_proctup;

	fc_proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcid));
	if (!HeapTupleIsValid(fc_proctup))
		PG_RETURN_NULL();

	if (((Form_pg_proc) GETSTRUCT(fc_proctup))->prokind == PROKIND_PROCEDURE)
	{
		ReleaseSysCache(fc_proctup);
		PG_RETURN_NULL();
	}

	initStringInfo(&fc_buf);

	fc_print_function_rettype(&fc_buf, fc_proctup);

	ReleaseSysCache(fc_proctup);

	PG_RETURN_TEXT_P(fc_string_to_text(fc_buf.data));
}

/* 
 * pg_get_function_result 的核心：将函数的返回类型附加到指定的缓冲区。
 */
static void fc_print_function_rettype(StringInfo fc_buf, HeapTuple fc_proctup)
{
	Form_pg_proc fc_proc = (Form_pg_proc) GETSTRUCT(fc_proctup);
	int			fc_ntabargs = 0;
	StringInfoData fc_rbuf;

	initStringInfo(&fc_rbuf);

	if (fc_proc->proretset)
	{
		/* 这可能是一个表函数；尝试打印参数 */
		appendStringInfoString(&fc_rbuf, "TABLE(");
		fc_ntabargs = fc_print_function_arguments(&fc_rbuf, fc_proctup, true, false);
		if (fc_ntabargs > 0)
			appendStringInfoChar(&fc_rbuf, ')');
		else
			resetStringInfo(&fc_rbuf);
	}

	if (fc_ntabargs == 0)
	{
		/* 不是表函数，因此执行常规操作 */
		if (fc_proc->proretset)
			appendStringInfoString(&fc_rbuf, "SETOF ");
		appendStringInfoString(&fc_rbuf, format_type_be(fc_proc->prorettype));
	}

	appendBinaryStringInfo(fc_buf, fc_rbuf.data, fc_rbuf.len);
}

/* 
 * pg_get_function_arguments 和 pg_get_function_result 的通用代码：
 * 将所需的参数子集附加到 buf。我们仅在 print_table_args 为 true 时打印 TABLE
 * 参数，在其为 false 时打印所有其他参数。仅在 print_defaults 为 true 时打印参数默认值。
 * 函数的返回值是打印的参数数量。
 */
static int fc_print_function_arguments(StringInfo fc_buf, HeapTuple fc_proctup,
						 bool fc_print_table_args, bool fc_print_defaults)
{
	Form_pg_proc fc_proc = (Form_pg_proc) GETSTRUCT(fc_proctup);
	int			fc_numargs;
	Oid		   *fc_argtypes;
	char	  **fc_argnames;
	char	   *fc_argmodes;
	int			fc_insertorderbyat = -1;
	int			fc_argsprinted;
	int			fc_inputargno;
	int			fc_nlackdefaults;
	List	   *fc_argdefaults = NIL;
	ListCell   *fc_nextargdefault = NULL;
	int			fc_i;

	fc_numargs = get_func_arg_info(fc_proctup,
								&fc_argtypes, &fc_argnames, &fc_argmodes);

	fc_nlackdefaults = fc_numargs;
	if (fc_print_defaults && fc_proc->pronargdefaults > 0)
	{
		Datum		fc_proargdefaults;
		bool		fc_isnull;

		fc_proargdefaults = SysCacheGetAttr(PROCOID, fc_proctup,
										 Anum_pg_proc_proargdefaults,
										 &fc_isnull);
		if (!fc_isnull)
		{
			char	   *fc_str;

			fc_str = TextDatumGetCString(fc_proargdefaults);
			fc_argdefaults = castNode(List, stringToNode(fc_str));
			pfree(fc_str);
			fc_nextargdefault = list_head(fc_argdefaults);
			/* nlackdefaults 仅计算没有默认值的 *输入* 参数 */
			fc_nlackdefaults = fc_proc->pronargs - list_length(fc_argdefaults);
		}
	}

	/* 检查有序集聚合的特殊处理 */
	if (fc_proc->prokind == PROKIND_AGGREGATE)
	{
		HeapTuple	fc_aggtup;
		Form_pg_aggregate fc_agg;

		fc_aggtup = SearchSysCache1(AGGFNOID, fc_proc->oid);
		if (!HeapTupleIsValid(fc_aggtup))
			elog(ERROR, "cache lookup failed for aggregate %u",
				 fc_proc->oid);
		fc_agg = (Form_pg_aggregate) GETSTRUCT(fc_aggtup);
		if (AGGKIND_IS_ORDERED_SET(fc_agg->aggkind))
			fc_insertorderbyat = fc_agg->aggnumdirectargs;
		ReleaseSysCache(fc_aggtup);
	}

	fc_argsprinted = 0;
	fc_inputargno = 0;
	for (fc_i = 0; fc_i < fc_numargs; fc_i++)
	{
		Oid			fc_argtype = fc_argtypes[fc_i];
		char	   *fc_argname = fc_argnames ? fc_argnames[fc_i] : NULL;
		char		fc_argmode = fc_argmodes ? fc_argmodes[fc_i] : PROARGMODE_IN;
		const char *fc_modename;
		bool		fc_isinput;

		switch (fc_argmode)
		{
			case PROARGMODE_IN:

				/* 
				 * 对于过程，显式标记所有参数模式，以避免与 DROP PROCEDURE 的 SQL 语法的歧义。
				 */
				if (fc_proc->prokind == PROKIND_PROCEDURE)
					fc_modename = "IN ";
				else
					fc_modename = "";
				fc_isinput = true;
				break;
			case PROARGMODE_INOUT:
				fc_modename = "INOUT ";
				fc_isinput = true;
				break;
			case PROARGMODE_OUT:
				fc_modename = "OUT ";
				fc_isinput = false;
				break;
			case PROARGMODE_VARIADIC:
				fc_modename = "VARIADIC ";
				fc_isinput = true;
				break;
			case PROARGMODE_TABLE:
				fc_modename = "";
				fc_isinput = false;
				break;
			default:
				elog(ERROR, "invalid parameter mode '%c'", fc_argmode);
				fc_modename = NULL;	/* 保持编译器安静 */
				fc_isinput = false;
				break;
		}
		if (fc_isinput)
			fc_inputargno++;		/* 这是一个基于 1 的计数器 */

		if (fc_print_table_args != (fc_argmode == PROARGMODE_TABLE))
			continue;

		if (fc_argsprinted == fc_insertorderbyat)
		{
			if (fc_argsprinted)
				appendStringInfoChar(fc_buf, ' ');
			appendStringInfoString(fc_buf, "ORDER BY ");
		}
		else if (fc_argsprinted)
			appendStringInfoString(fc_buf, ", ");

		appendStringInfoString(fc_buf, fc_modename);
		if (fc_argname && fc_argname[0])
			appendStringInfo(fc_buf, "%s ", quote_identifier(fc_argname));
		appendStringInfoString(fc_buf, format_type_be(fc_argtype));
		if (fc_print_defaults && fc_isinput && fc_inputargno > fc_nlackdefaults)
		{
			Node	   *fc_expr;

			Assert(fc_nextargdefault != NULL);
			fc_expr = (Node *) lfirst(fc_nextargdefault);
			fc_nextargdefault = lnext(fc_argdefaults, fc_nextargdefault);

			appendStringInfo(fc_buf, " DEFAULT %s",
							 deparse_expression(fc_expr, NIL, false, false));
		}
		fc_argsprinted++;

		/* 恶劣的黑客：对可变参数的有序集聚合打印最后一个参数两次 */
		if (fc_argsprinted == fc_insertorderbyat && fc_i == fc_numargs - 1)
		{
			fc_i--;
			/* 聚合不应有默认值，但为了保险起见 ... */
			fc_print_defaults = false;
		}
	}

	return fc_argsprinted;
}

static bool fc_is_input_argument(int fc_nth, const char *fc_argmodes)
{
	return (!fc_argmodes
			|| fc_argmodes[fc_nth] == PROARGMODE_IN
			|| fc_argmodes[fc_nth] == PROARGMODE_INOUT
			|| fc_argmodes[fc_nth] == PROARGMODE_VARIADIC);
}

/* 
 * 将使用的转换类型附加到指定缓冲区
 */
static void fc_print_function_trftypes(StringInfo fc_buf, HeapTuple fc_proctup)
{
	Oid		   *fc_trftypes;
	int			fc_ntypes;

	fc_ntypes = get_func_trftypes(fc_proctup, &fc_trftypes);
	if (fc_ntypes > 0)
	{
		int			fc_i;

		appendStringInfoString(fc_buf, " TRANSFORM ");
		for (fc_i = 0; fc_i < fc_ntypes; fc_i++)
		{
			if (fc_i != 0)
				appendStringInfoString(fc_buf, ", ");
			appendStringInfo(fc_buf, "FOR TYPE %s", format_type_be(fc_trftypes[fc_i]));
		}
		appendStringInfoChar(fc_buf, '\n');
	}
}

/* 
 * 获取函数参数默认值的文本表示。该函数的第二个参数是所有参数中的参数编号
 * （即 proallargtypes，而不是 proargtypes），从 1 开始，因为就是这样的信息_schema.sql 使用它。
 */
Datum pg_get_function_arg_default(PG_FUNCTION_ARGS)
{
	Oid			fc_funcid = PG_GETARG_OID(0);
	int32		fc_nth_arg = PG_GETARG_INT32(1);
	HeapTuple	fc_proctup;
	Form_pg_proc fc_proc;
	int			fc_numargs;
	Oid		   *fc_argtypes;
	char	  **fc_argnames;
	char	   *fc_argmodes;
	int			fc_i;
	List	   *fc_argdefaults;
	Node	   *fc_node;
	char	   *fc_str;
	int			fc_nth_inputarg;
	Datum		fc_proargdefaults;
	bool		fc_isnull;
	int			fc_nth_default;

	fc_proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcid));
	if (!HeapTupleIsValid(fc_proctup))
		PG_RETURN_NULL();

	fc_numargs = get_func_arg_info(fc_proctup, &fc_argtypes, &fc_argnames, &fc_argmodes);
	if (fc_nth_arg < 1 || fc_nth_arg > fc_numargs || !fc_is_input_argument(fc_nth_arg - 1, fc_argmodes))
	{
		ReleaseSysCache(fc_proctup);
		PG_RETURN_NULL();
	}

	fc_nth_inputarg = 0;
	for (fc_i = 0; fc_i < fc_nth_arg; fc_i++)
		if (fc_is_input_argument(fc_i, fc_argmodes))
			fc_nth_inputarg++;

	fc_proargdefaults = SysCacheGetAttr(PROCOID, fc_proctup,
									 Anum_pg_proc_proargdefaults,
									 &fc_isnull);
	if (fc_isnull)
	{
		ReleaseSysCache(fc_proctup);
		PG_RETURN_NULL();
	}

	fc_str = TextDatumGetCString(fc_proargdefaults);
	fc_argdefaults = castNode(List, stringToNode(fc_str));
	pfree(fc_str);

	fc_proc = (Form_pg_proc) GETSTRUCT(fc_proctup);

	/* 
	 * 计算 proargdefaults 的索引：proargdefaults 对应于
	 * 最后 N 个输入参数，其中 N = pronargdefaults。
	 */
	fc_nth_default = fc_nth_inputarg - 1 - (fc_proc->pronargs - fc_proc->pronargdefaults);

	if (fc_nth_default < 0 || fc_nth_default >= list_length(fc_argdefaults))
	{
		ReleaseSysCache(fc_proctup);
		PG_RETURN_NULL();
	}
	fc_node = list_nth(fc_argdefaults, fc_nth_default);
	fc_str = deparse_expression(fc_node, NIL, false, false);

	ReleaseSysCache(fc_proctup);

	PG_RETURN_TEXT_P(fc_string_to_text(fc_str));
}

static void fc_print_function_sqlbody(StringInfo fc_buf, HeapTuple fc_proctup)
{
	int			fc_numargs;
	Oid		   *fc_argtypes;
	char	  **fc_argnames;
	char	   *fc_argmodes;
	deparse_namespace fc_dpns = {0};
	Datum		fc_tmp;
	bool		fc_isnull;
	Node	   *fc_n;

	fc_dpns.funcname = pstrdup(NameStr(((Form_pg_proc) GETSTRUCT(fc_proctup))->proname));
	fc_numargs = get_func_arg_info(fc_proctup,
								&fc_argtypes, &fc_argnames, &fc_argmodes);
	fc_dpns.numargs = fc_numargs;
	fc_dpns.argnames = fc_argnames;

	fc_tmp = SysCacheGetAttr(PROCOID, fc_proctup, Anum_pg_proc_prosqlbody, &fc_isnull);
	Assert(!fc_isnull);
	fc_n = stringToNode(TextDatumGetCString(fc_tmp));

	if (IsA(fc_n, List))
	{
		List	   *fc_stmts;
		ListCell   *fc_lc;

		fc_stmts = linitial(castNode(List, fc_n));

		appendStringInfoString(fc_buf, "BEGIN ATOMIC\n");

		foreach(fc_lc, fc_stmts)
		{
			Query	   *fc_query = lfirst_node(Query, fc_lc);

			/* 看来最好在 rels 上获得至少 AccessShareLock */
			AcquireRewriteLocks(fc_query, false, false);
			get_query_def(fc_query, fc_buf, list_make1(&fc_dpns), NULL, false,
						  PRETTYFLAG_INDENT, WRAP_COLUMN_DEFAULT, 1);
			appendStringInfoChar(fc_buf, ';');
			appendStringInfoChar(fc_buf, '\n');
		}

		appendStringInfoString(fc_buf, "END");
	}
	else
	{
		Query	   *fc_query = castNode(Query, fc_n);

		/* 看来最好在 rels 上获得至少 AccessShareLock */
		AcquireRewriteLocks(fc_query, false, false);
		get_query_def(fc_query, fc_buf, list_make1(&fc_dpns), NULL, false,
					  0, WRAP_COLUMN_DEFAULT, 0);
	}
}

Datum pg_get_function_sqlbody(PG_FUNCTION_ARGS)
{
	Oid			fc_funcid = PG_GETARG_OID(0);
	StringInfoData fc_buf;
	HeapTuple	fc_proctup;
	bool		fc_isnull;

	initStringInfo(&fc_buf);

	/* 查找函数 */
	fc_proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcid));
	if (!HeapTupleIsValid(fc_proctup))
		PG_RETURN_NULL();

	(void) SysCacheGetAttr(PROCOID, fc_proctup, Anum_pg_proc_prosqlbody, &fc_isnull);
	if (fc_isnull)
	{
		ReleaseSysCache(fc_proctup);
		PG_RETURN_NULL();
	}

	fc_print_function_sqlbody(&fc_buf, fc_proctup);

	ReleaseSysCache(fc_proctup);

	PG_RETURN_TEXT_P(cstring_to_text(fc_buf.data));
}


/* 
 * deparse_expression			- 用于反解析表达式的通用工具
 *
 * 调用 deparse_expression_pretty，所有 prettyPrinting 被禁用
 */
char * deparse_expression(Node *fc_expr, List *fc_dpcontext,
				   bool fc_forceprefix, bool fc_showimplicit)
{
	return fc_deparse_expression_pretty(fc_expr, fc_dpcontext, fc_forceprefix,
									 fc_showimplicit, 0, 0);
}

/* ----------
 * deparse_expression_pretty	- 用于反解析表达式的通用工具
 *
 * expr 是要反解析的节点树。它必须是一个转换过的表达式
 * 树（即，不是 gram.y 的原始输出）。
 *
 * dpcontext 是一个 deparse_namespace 节点的列表，代表解释
 * 节点树中 Vars 的上下文。如果没有期待的 Vars，可以为 NIL。
 *
 * forceprefix 为 true 时，强制所有 Vars 以其表名作为前缀。
 *
 * showimplicit 为 true 时，强制所有隐式转换显式显示。
 *
 * 尝试根据 prettyFlags 和 startIndent 美化输出。
 *
 * 结果是一个 palloc' 的字符串。
 * ----------
 */
static char * fc_deparse_expression_pretty(Node *fc_expr, List *fc_dpcontext,
						  bool fc_forceprefix, bool fc_showimplicit,
						  int fc_prettyFlags, int fc_startIndent)
{
	StringInfoData fc_buf;
	deparse_context fc_context;

	initStringInfo(&fc_buf);
	fc_context.buf = &fc_buf;
	fc_context.namespaces = fc_dpcontext;
	fc_context.windowClause = NIL;
	fc_context.windowTList = NIL;
	fc_context.varprefix = fc_forceprefix;
	fc_context.prettyFlags = fc_prettyFlags;
	fc_context.wrapColumn = WRAP_COLUMN_DEFAULT;
	fc_context.indentLevel = fc_startIndent;
	fc_context.special_exprkind = EXPR_KIND_NONE;
	fc_context.appendparents = NULL;

	get_rule_expr(fc_expr, &fc_context, fc_showimplicit);

	return fc_buf.data;
}

/* ----------
 * deparse_context_for			- 为单个关系构建反解析上下文
 *
 * 给定关系的引用名称（别名）和 OID，为仅引用该关系的表达式构建反解析
 * 上下文（作为 varno 1，varlevelsup 0）。这对于许多 deparse_expression 的用途是足够的。
 * ----------
 */
List * deparse_context_for(const char *fc_aliasname, Oid fc_relid)
{
	deparse_namespace *fc_dpns;
	RangeTblEntry *fc_rte;

	fc_dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace));

	/* 为关系构建一个最小的RTE */
	fc_rte = makeNode(RangeTblEntry);
	fc_rte->rtekind = RTE_RELATION;
	fc_rte->relid = fc_relid;
	fc_rte->relkind = RELKIND_RELATION;	/* 此处无需精确 */
	fc_rte->rellockmode = AccessShareLock;
	fc_rte->alias = makeAlias(fc_aliasname, NIL);
	fc_rte->eref = fc_rte->alias;
	fc_rte->lateral = false;
	fc_rte->inh = false;
	fc_rte->inFromCl = true;

	/* 构建一元素的 rtable */
	fc_dpns->rtable = list_make1(fc_rte);
	fc_dpns->subplans = NIL;
	fc_dpns->ctes = NIL;
	fc_dpns->appendrels = NULL;
	set_rtable_names(fc_dpns, NIL, NULL);
	set_simple_column_names(fc_dpns);

	/* 返回一个一深的命名空间堆栈 */
	return list_make1(fc_dpns);
}

/* 
 * deparse_context_for_plan_tree - 为计划树建立反解析上下文
 *
 * 在计划树中反解析表达式时，我们使用计划的 rangetable
 * 来解析简单 Vars 的名称。对于 rangetable 较大的情况，其列名的初始化相当昂贵，并且对于计划树中的每条表达式都是一样的；因此我们只进行一次并为每个表达式重用
 * 该函数的结果。（注意，结果在应用 set_deparse_context_plan() 之前不可用。）
 *
 * 除了 PlannedStmt，传递之前调用 select_rtable_names_for_explain 分配的每个 RTE
 * 别名。
 */
List * deparse_context_for_plan_tree(PlannedStmt *fc_pstmt, List *fc_rtable_names)
{
	deparse_namespace *fc_dpns;

	fc_dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace));

	/* 初始化在整个计划树中保持不变的字段 */
	fc_dpns->rtable = fc_pstmt->rtable;
	fc_dpns->rtable_names = fc_rtable_names;
	fc_dpns->subplans = fc_pstmt->subplans;
	fc_dpns->ctes = NIL;
	if (fc_pstmt->appendRelations)
	{
		/* 设置数组，以子关系 ID 为索引 */
		int			fc_ntables = list_length(fc_dpns->rtable);
		ListCell   *fc_lc;

		fc_dpns->appendrels = (AppendRelInfo **)
			palloc0((fc_ntables + 1) * sizeof(AppendRelInfo *));
		foreach(fc_lc, fc_pstmt->appendRelations)
		{
			AppendRelInfo *fc_appinfo = lfirst_node(AppendRelInfo, fc_lc);
			Index		fc_crelid = fc_appinfo->child_relid;

			Assert(fc_crelid > 0 && fc_crelid <= fc_ntables);
			Assert(fc_dpns->appendrels[fc_crelid] == NULL);
			fc_dpns->appendrels[fc_crelid] = fc_appinfo;
		}
	}
	else
		fc_dpns->appendrels = NULL;	/* 不需要它 */

	/*
	 * 设置列名别名。我们对于连接 RTEs 将得到相当虚假的结果，但这没关系，因为计划树不包含任何连接别名变量。
	 */
	set_simple_column_names(fc_dpns);

	/* 返回一个一深的命名空间堆栈 */
	return list_make1(fc_dpns);
}

/*
 * set_deparse_context_plan - 指定包含表达式的计划节点
 *
 * 当在计划树中反解析表达式时，我们可能需要解析 OUTER_VAR、INNER_VAR 或 INDEX_VAR 引用。为此，调用者必须提供父计划节点。然后，可以通过深入到左子计划和右子计划来解析 OUTER_VAR 和 INNER_VAR 引用。类似地，可以通过参考给定在父 IndexOnlyScan 节点中的 indextlist，或者在 ForeignScan 和 CustomScan 节点的扫描 tlist 中解析 INDEX_VAR 引用。（注意，我们当前不支持在常规 IndexScan 或 BitmapIndexScan 节点中反解析 indexquals；对于这些，我们只能反解析 indexqualorig 字段，这将不包含 INDEX_VAR 变量。）
 *
 * 祖先列表是计划的父计划和子计划节点的列表，最紧密嵌套的在前。这是解析 PARAM_EXEC 参数所需的。注意，我们假设所有计划节点共享相同的 rtable。
 *
 * 一旦调用了此函数，deparse_expression() 可以在指定计划节点的附属表达式上调用。要反解析同一计划树中另一个计划节点的表达式，请重新调用此函数以识别新的父计划节点。
 *
 * 结果是相同的列表；这是一个表示上的便利。
 */
List * set_deparse_context_plan(List *fc_dpcontext, Plan *fc_plan, List *fc_ancestors)
{
	deparse_namespace *fc_dpns;

	/* 应始终为计划去解析拥有一个条目命名空间列表 */
	Assert(list_length(fc_dpcontext) == 1);
	fc_dpns = (deparse_namespace *) linitial(fc_dpcontext);

	/* 将我们的注意力集中在传入的特定计划节点上 */
	fc_dpns->ancestors = fc_ancestors;
	set_deparse_plan(fc_dpns, fc_plan);

	return fc_dpcontext;
}

/*
 * select_rtable_names_for_explain - 选择用于EXPLAIN的RTE别名
 *
 * 确定我们在EXPLAIN操作中将使用的关系别名。
 * 这只是set_rtable_names的前端。我们必须将别名暴露给EXPLAIN，因为EXPLAIN需要知道正确的别名以进行打印。
 */
List * select_rtable_names_for_explain(List *fc_rtable, Bitmapset *fc_rels_used)
{
	deparse_namespace fc_dpns;

	memset(&fc_dpns, 0, sizeof(fc_dpns));
	fc_dpns.rtable = fc_rtable;
	fc_dpns.subplans = NIL;
	fc_dpns.ctes = NIL;
	fc_dpns.appendrels = NULL;
	set_rtable_names(&fc_dpns, NIL, fc_rels_used);
	/* 我们现在不必费心计算列别名 */

	return fc_dpns.rtable_names;
}

/*
 * set_rtable_names: 选择要在打印查询中使用的RTE别名
 *
 * 我们用一份与已填写的dpns->rtable列表一一对应的名称列表填充dpns->rtable_names。
 * 每个RTE名称在新命名空间及其在parent_namespaces中列出的任何祖先命名空间中都是唯一的。
 *
 * 如果rels_used不为NULL，则仅为列在其中的RTE索引赋予别名。
 *
 * 请注意，此函数仅关注关系名称，而不关注列名称。
 */
static void set_rtable_names(deparse_namespace *fc_dpns, List *fc_parent_namespaces,
				 Bitmapset *fc_rels_used)
{
	HASHCTL		fc_hash_ctl;
	HTAB	   *fc_names_hash;
	NameHashEntry *fc_hentry;
	bool		fc_found;
	int			fc_rtindex;
	ListCell   *fc_lc;

	fc_dpns->rtable_names = NIL;
	/* 如果rtable为空，则无需更多操作 */
	if (fc_dpns->rtable == NIL)
		return;

	/*
	 * 我们使用哈希表来保存已知名称，以使该过程为O(N)，而不是O(N^2)，其中N为名称数。
	 */
	fc_hash_ctl.keysize = NAMEDATALEN;
	fc_hash_ctl.entrysize = sizeof(NameHashEntry);
	fc_hash_ctl.hcxt = CurrentMemoryContext;
	fc_names_hash = hash_create("set_rtable_names names",
							 list_length(fc_dpns->rtable),
							 &fc_hash_ctl,
							 HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);

	/* 预加载哈希表，以便在parent_namespaces中出现名称 */
	foreach(fc_lc, fc_parent_namespaces)
	{
		deparse_namespace *fc_olddpns = (deparse_namespace *) lfirst(fc_lc);
		ListCell   *fc_lc2;

		foreach(fc_lc2, fc_olddpns->rtable_names)
		{
			char	   *fc_oldname = (char *) lfirst(fc_lc2);

			if (fc_oldname == NULL)
				continue;
			fc_hentry = (NameHashEntry *) hash_search(fc_names_hash,
												   fc_oldname,
												   HASH_ENTER,
												   &fc_found);
			/* 我们不抱怨parent namespaces中的重复名称 */
			fc_hentry->counter = 0;
		}
	}

	/* 现在我们可以扫描rtable */
	fc_rtindex = 1;
	foreach(fc_lc, fc_dpns->rtable)
	{
		RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_lc);
		char	   *fc_refname;

		/* 以防这需要不合理的时间... */
		CHECK_FOR_INTERRUPTS();

		if (fc_rels_used && !bms_is_member(fc_rtindex, fc_rels_used))
		{
			/* 忽略未引用的RTE */
			fc_refname = NULL;
		}
		else if (fc_rte->alias)
		{
			/* 如果RTE具有用户定义的别名，请优先使用 */
			fc_refname = fc_rte->alias->aliasname;
		}
		else if (fc_rte->rtekind == RTE_RELATION)
		{
			/* 使用关系的当前实际名称 */
			fc_refname = get_rel_name(fc_rte->relid);
		}
		else if (fc_rte->rtekind == RTE_JOIN)
		{
			/* 未命名的连接没有refname */
			fc_refname = NULL;
		}
		else
		{
			/* 否则使用解析器分配的名称 */
			fc_refname = fc_rte->eref->aliasname;
		}

		/*
		 * 如果选择的名称不唯一，则附加数字使其成为唯一，并在获得唯一名称后为其创建新的哈希条目。
		 * 对于非常长的输入名称，我们可能需要截断以保持在NAMEDATALEN范围内。
		 */
		if (fc_refname)
		{
			fc_hentry = (NameHashEntry *) hash_search(fc_names_hash,
												   fc_refname,
												   HASH_ENTER,
												   &fc_found);
			if (fc_found)
			{
				/* 名称已在使用中，必须选择一个新名称 */
				int			fc_refnamelen = strlen(fc_refname);
				char	   *fc_modname = (char *) palloc(fc_refnamelen + 16);
				NameHashEntry *fc_hentry2;

				do
				{
					fc_hentry->counter++;
					for (;;)
					{
						memcpy(fc_modname, fc_refname, fc_refnamelen);
						sprintf(fc_modname + fc_refnamelen, "_%d", fc_hentry->counter);
						if (strlen(fc_modname) < NAMEDATALEN)
							break;
						/* 从refname中删除字符以保留所有数字 */
						fc_refnamelen = pg_mbcliplen(fc_refname, fc_refnamelen,
												  fc_refnamelen - 1);
					}
					fc_hentry2 = (NameHashEntry *) hash_search(fc_names_hash,
															fc_modname,
															HASH_ENTER,
															&fc_found);
				} while (fc_found);
				fc_hentry2->counter = 0;	/* 初始化新哈希条目 */
				fc_refname = fc_modname;
			}
			else
			{
				/* 名称此前未使用，仅需初始化hentry */
				fc_hentry->counter = 0;
			}
		}

		fc_dpns->rtable_names = lappend(fc_dpns->rtable_names, fc_refname);
		fc_rtindex++;
	}

	hash_destroy(fc_names_hash);
}

/*
 * set_deparse_for_query: 为去解析查询树设置deparse_namespace
 *
 * 为了方便，这被定义为从头初始化deparse_namespace结构。
 */
static void set_deparse_for_query(deparse_namespace *fc_dpns, Query *fc_query,
					  List *fc_parent_namespaces)
{
	ListCell   *fc_lc;
	ListCell   *fc_lc2;

	/* 初始化*dpns并填充rtable/ctes链接 */
	memset(fc_dpns, 0, sizeof(deparse_namespace));
	fc_dpns->rtable = fc_query->rtable;
	fc_dpns->subplans = NIL;
	fc_dpns->ctes = fc_query->cteList;
	fc_dpns->appendrels = NULL;

	/* 为每个RTE分配一个唯一的关系别名 */
	set_rtable_names(fc_dpns, fc_parent_namespaces, NULL);

	/* 初始化dpns->rtable_columns以包含零结构 */
	fc_dpns->rtable_columns = NIL;
	while (list_length(fc_dpns->rtable_columns) < list_length(fc_dpns->rtable))
		fc_dpns->rtable_columns = lappend(fc_dpns->rtable_columns,
									   palloc0(sizeof(deparse_columns)));

	/* 如果它是一个实用程序查询，它将没有连接树 */
	if (fc_query->jointree)
	{
		/* 检测是否需要USING名称的全局唯一性 */
		fc_dpns->unique_using =
			fc_has_dangerous_join_using(fc_dpns, (Node *) fc_query->jointree);

		/*
		 * 通过对查询连接树的递归遍历，为使用USING合并的列选择名称。
		 */
		set_using_names(fc_dpns, (Node *) fc_query->jointree, NIL);
	}

	/*
	 * 现在为每个RTE分配剩余的列别名。我们在rtable的线性扫描中完成此操作，因此可以处理无论其是否在连接树中的RTE。
	 * （我们不能错过NEW.*, INSERT目标关系等）。JOIN RTE必须在其子项之后处理，但这没关系，因为它们出现在rtable列表中晚于其子项
	 * （参考identify_join_columns()中的断言）。
	 */
	forboth(fc_lc, fc_dpns->rtable, fc_lc2, fc_dpns->rtable_columns)
	{
		RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_lc);
		deparse_columns *fc_colinfo = (deparse_columns *) lfirst(fc_lc2);

		if (fc_rte->rtekind == RTE_JOIN)
			set_join_column_names(fc_dpns, fc_rte, fc_colinfo);
		else
			set_relation_column_names(fc_dpns, fc_rte, fc_colinfo);
	}
}

/*
 * set_simple_column_names: 为非查询情况填充列别名
 *
 * 这处理EXPLAIN和只有关系RTE的情况。
 * 没有连接树，我们无法对此类JOIN RTE进行智能处理，但我们不需要（请注意，EXPLAIN无论如何不应看到连接别名Vars）。
 * 如果我们遇到JOIN RTE，我们将像处理非表基础RTE一样处理它。
 */
static void set_simple_column_names(deparse_namespace *fc_dpns)
{
	ListCell   *fc_lc;
	ListCell   *fc_lc2;

	/* 初始化dpns->rtable_columns以包含零结构 */
	fc_dpns->rtable_columns = NIL;
	while (list_length(fc_dpns->rtable_columns) < list_length(fc_dpns->rtable))
		fc_dpns->rtable_columns = lappend(fc_dpns->rtable_columns,
									   palloc0(sizeof(deparse_columns)));

	/* 在每个 RTE 内分配唯一的列别名 */
	forboth(fc_lc, fc_dpns->rtable, fc_lc2, fc_dpns->rtable_columns)
	{
		RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_lc);
		deparse_columns *fc_colinfo = (deparse_columns *) lfirst(fc_lc2);

		set_relation_column_names(fc_dpns, fc_rte, fc_colinfo);
	}
}

/*
 * has_dangerous_join_using: 搜索 jointree 中未命名的 JOIN USING
 *
 * JOIN USING 的合并列可能与任何输入列的表现不同，
 * 要么是因为它们与 COALESCE 合并（在 FULL JOIN 中），
 * 要么是因为需要对底层输入列进行隐式强制类型转换。
 * 在这种情况下，列必须作为 JOIN 的列进行引用，而不是作为
 * 任何输入的列。如果连接是未命名的（没有别名），这将有问题：
 * 我们无法使用 RTE 名称来限定列名，因为没有。 (强行为连接分配别名
 * 不是解决方案，因为这将阻止对连接下方的表进行合法引用。)
 * 为确保查询中的每一列都可以明确引用，
 * 我们必须为这些合并列分配在整个查询中全局唯一的名称，
 * 并根据需要为其他列分配别名。
 *
 * 由于随后的重新别名对查询的可读性相当有害，
 * 我们不会在没有必要的情况下执行此操作。因此，
 * 在开始 set_using_names() 之前，我们必须预扫描连接树，
 * 以查看是否需要这样做。 */
static bool fc_has_dangerous_join_using(deparse_namespace *fc_dpns, Node *fc_jtnode)
{
	if (IsA(fc_jtnode, RangeTblRef))
	{
		/* 此处无操作 */
	}
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		ListCell   *fc_lc;

		foreach(fc_lc, fc_f->fromlist)
		{
			if (fc_has_dangerous_join_using(fc_dpns, (Node *) lfirst(fc_lc)))
				return true;
		}
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;

		/* 这是一个带有 USING 的未命名 JOIN 吗？ */
		if (fc_j->alias == NULL && fc_j->usingClause)
		{
			/*
			 * 是的，所以检查每个连接别名变量，看看它们是否不是简单的
			 * 引用底层列。如果是的话，我们就有一个危险的情况，
			 * 必须选择唯一的别名。
			 */
			RangeTblEntry *fc_jrte = rt_fetch(fc_j->rtindex, fc_dpns->rtable);

			/* 我们只需检查合并列 */
			for (int fc_i = 0; fc_i < fc_jrte->joinmergedcols; fc_i++)
			{
				Node	   *fc_aliasvar = list_nth(fc_jrte->joinaliasvars, fc_i);

				if (!IsA(fc_aliasvar, Var))
					return true;
			}
		}

		/* 不，不过检查子项 */
		if (fc_has_dangerous_join_using(fc_dpns, fc_j->larg))
			return true;
		if (fc_has_dangerous_join_using(fc_dpns, fc_j->rarg))
			return true;
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
	return false;
}

/*
 * set_using_names: 选择用于合并 USING 列的列别名
 *
 * 我们在查询 jointree 的递归下降过程中执行此操作。
 * dpns->unique_using 必须已经设置，以确定全局策略。
 *
 * 列别名信息保存在 dpns->rtable_columns 列表中，
 * 假设该列表已填充为预先归零的 deparse_columns 结构。
 *
 * parentUsing 是当前 jointree 节点的父连接中分配的所有 USING 别名的列表。
 * （传入的列表不得修改。） */
static void set_using_names(deparse_namespace *fc_dpns, Node *fc_jtnode, List *fc_parentUsing)
{
	if (IsA(fc_jtnode, RangeTblRef))
	{
		/* 现在没什么可做的 */
	}
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		ListCell   *fc_lc;

		foreach(fc_lc, fc_f->fromlist)
			set_using_names(fc_dpns, (Node *) lfirst(fc_lc), fc_parentUsing);
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;
		RangeTblEntry *fc_rte = rt_fetch(fc_j->rtindex, fc_dpns->rtable);
		deparse_columns *fc_colinfo = deparse_columns_fetch(fc_j->rtindex, fc_dpns);
		int		   *fc_leftattnos;
		int		   *fc_rightattnos;
		deparse_columns *fc_leftcolinfo;
		deparse_columns *fc_rightcolinfo;
		int			fc_i;
		ListCell   *fc_lc;

		/* 获取连接形状的信息 */
		fc_identify_join_columns(fc_j, fc_rte, fc_colinfo);
		fc_leftattnos = fc_colinfo->leftattnos;
		fc_rightattnos = fc_colinfo->rightattnos;

		/* 查找尚未填充的子 deparse_columns 结构 */
		fc_leftcolinfo = deparse_columns_fetch(fc_colinfo->leftrti, fc_dpns);
		fc_rightcolinfo = deparse_columns_fetch(fc_colinfo->rightrti, fc_dpns);

		/*
		 * 如果此连接是未命名的，那么我们无法在此级别替换新别名，
		 * 所以任何下推到这里的名称要求必须再次下推到子项。
		 */
		if (fc_rte->alias == NULL)
		{
			for (fc_i = 0; fc_i < fc_colinfo->num_cols; fc_i++)
			{
				char	   *fc_colname = fc_colinfo->colnames[fc_i];

				if (fc_colname == NULL)
					continue;

				/* 除非是系统列，否则向左列下推 */
				if (fc_leftattnos[fc_i] > 0)
				{
					fc_expand_colnames_array_to(fc_leftcolinfo, fc_leftattnos[fc_i]);
					fc_leftcolinfo->colnames[fc_leftattnos[fc_i] - 1] = fc_colname;
				}

				/* 在右侧同样 */
				if (fc_rightattnos[fc_i] > 0)
				{
					fc_expand_colnames_array_to(fc_rightcolinfo, fc_rightattnos[fc_i]);
					fc_rightcolinfo->colnames[fc_rightattnos[fc_i] - 1] = fc_colname;
				}
			}
		}

		/*
		 * 如果有 USING 子句，选择 USING 列名称并将这些名称下推到子项。
		 * 我们有两种策略：
		 *
		 * 如果 dpns->unique_using 为真，我们强制所有 USING 名称
		 * 在整个查询级别上都是唯一的。原则上，我们只需要
		 * 危险的 USING 列的名称在全局上是唯一的，但为了
		 * 安全地在一次传递中分配所有 USING 名称，我们必须为所有名称
		 * 强制执行相同的唯一性规则。然而，如果一个 USING
		 * 列的名称是从父级下推的，我们应该直接使用它，而不是进行唯一性调整。
		 * 这在我们处于未命名连接时是必要的，并且不会产生歧义的风险。
		 * 此外，如果有用户编写的输出别名用于合并列，我们更倾向于使用
		 * 该别名，而不是输入名称；这简化了逻辑，并且似乎会导致
		 * 更少的别名化。
		 *
		 * 如果 dpns->unique_using 为假，则我们只需要 USING 名称在
		 * 各自的连接 RTE 内唯一。我们仍然需要尊重下推的名称，
		 * 虽然。
		 *
		 * 虽然结果显著不同，但这两种策略由相同的代码实现，
		 * 只是分配名称是否放入 dpns->using_names 的区别。 */
		if (fc_j->usingClause)
		{
			/* 复制输入的 parentUsing 列表，以免我们修改它 */
			fc_parentUsing = list_copy(fc_parentUsing);

			/* USING 名称必须对应于第一个连接输出列 */
			fc_expand_colnames_array_to(fc_colinfo, list_length(fc_j->usingClause));
			fc_i = 0;
			foreach(fc_lc, fc_j->usingClause)
			{
				char	   *fc_colname = strVal(lfirst(fc_lc));

				/* 断言这是一个合并列 */
				Assert(fc_leftattnos[fc_i] != 0 && fc_rightattnos[fc_i] != 0);

				/* 如果有的话，采用传递下来的名称，否则选择唯一名称 */
				if (fc_colinfo->colnames[fc_i] != NULL)
					fc_colname = fc_colinfo->colnames[fc_i];
				else
				{
					/* 如果有用户编写的输出别名，则优先使用 */
					if (fc_rte->alias && fc_i < list_length(fc_rte->alias->colnames))
						fc_colname = strVal(list_nth(fc_rte->alias->colnames, fc_i));
					/* 使之适当唯一 */
					fc_colname = fc_make_colname_unique(fc_colname, fc_dpns, fc_colinfo);
					if (fc_dpns->unique_using)
						fc_dpns->using_names = lappend(fc_dpns->using_names,
													fc_colname);
					/* 也将其保存为输出列名称 */
					fc_colinfo->colnames[fc_i] = fc_colname;
				}

				/* 记住选定的名称以供后用 */
				fc_colinfo->usingNames = lappend(fc_colinfo->usingNames, fc_colname);
				fc_parentUsing = lappend(fc_parentUsing, fc_colname);

				/* 除非是系统列，否则向左列下推 */
				if (fc_leftattnos[fc_i] > 0)
				{
					fc_expand_colnames_array_to(fc_leftcolinfo, fc_leftattnos[fc_i]);
					fc_leftcolinfo->colnames[fc_leftattnos[fc_i] - 1] = fc_colname;
				}

				/* 在右侧同样 */
				if (fc_rightattnos[fc_i] > 0)
				{
					fc_expand_colnames_array_to(fc_rightcolinfo, fc_rightattnos[fc_i]);
					fc_rightcolinfo->colnames[fc_rightattnos[fc_i] - 1] = fc_colname;
				}

				fc_i++;
			}
		}

		/* 用正确的 parentUsing 信息标记子 deparse_columns 结构 */
		fc_leftcolinfo->parentUsing = fc_parentUsing;
		fc_rightcolinfo->parentUsing = fc_parentUsing;

		/* 现在在子项中递归分配 USING 列名称 */
		set_using_names(fc_dpns, fc_j->larg, fc_parentUsing);
		set_using_names(fc_dpns, fc_j->rarg, fc_parentUsing);
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
}

/*
 * set_relation_column_names: 为非连接 RTE 选择列别名
 *
 * 列别名信息保存在 *colinfo 中，假设该信息已预先归零。
 * 如果任何 colnames 条目已填充，这些将覆盖本地选择。 */
static void set_relation_column_names(deparse_namespace *fc_dpns, RangeTblEntry *fc_rte,
						  deparse_columns *fc_colinfo)
{
	int			fc_ncolumns;
	char	  **fc_real_colnames;
	bool		fc_changed_any;
	int			fc_noldcolumns;
	int			fc_i;
	int			fc_j;

	/*
	 * 构造当前 RTE 的“真实”列名称数组。
	 * real_colnames[] 将按物理列号索引，对于已删除的列
	 * 采用 NULL 条目。 */
	if (fc_rte->rtekind == RTE_RELATION)
	{
		
/* 关系 --- 查看系统目录以获取最新信息 */
		Relation	fc_rel;
		TupleDesc	fc_tupdesc;

		fc_rel = relation_open(fc_rte->relid, AccessShareLock);
		fc_tupdesc = RelationGetDescr(fc_rel);

		fc_ncolumns = fc_tupdesc->natts;
		fc_real_colnames = (char **) palloc(fc_ncolumns * sizeof(char *));

		for (fc_i = 0; fc_i < fc_ncolumns; fc_i++)
		{
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupdesc, fc_i);

			if (fc_attr->attisdropped)
				fc_real_colnames[fc_i] = NULL;
			else
				fc_real_colnames[fc_i] = pstrdup(NameStr(fc_attr->attname));
		}
		relation_close(fc_rel, AccessShareLock);
	}
	else
	{
		/* 否则从 eref 或 expandRTE() 获取列名 */
		List	   *fc_colnames;
		ListCell   *fc_lc;

		/*
		 * 返回复合类型的函数具有烦人的属性，因为某些
		 * 复合类型的列可能在查询解析后被删除。 如果可能，请使用 expandRTE() 来处理该
		 * 情况，因为它有必要的繁琐逻辑来了解
		 * 被删除的列。 然而，如果我们正在解释一个计划，那么我们
		 * 没有 rte->functions，因为规划器认为以后不需要
		 * 它，这会破坏 expandRTE()。 所以在这种情况下，我们不得不依赖 rte->eref，这可能导致我们报告一个被删除
		 * 列的旧名称；这对于 EXPLAIN 的目的来说似乎足够接近。
		 *
		 * 对于非-RELATION，非-FUNCTION 的 RTE，我们可以直接查看 rte->eref，
		 * 这应该是足够最新的：没有其他 RTE 类型
		 * 可以在解析后删除列。
		 */
		if (fc_rte->rtekind == RTE_FUNCTION && fc_rte->functions != NIL)
		{
			/* 由于我们没有创建 Vars，rtindex 等无关紧要 */
			expandRTE(fc_rte, 1, 0, -1, true /* 包括被丢弃的列 */ ,
					  &fc_colnames, NULL);
		}
		else
			fc_colnames = fc_rte->eref->colnames;

		fc_ncolumns = list_length(fc_colnames);
		fc_real_colnames = (char **) palloc(fc_ncolumns * sizeof(char *));

		fc_i = 0;
		foreach(fc_lc, fc_colnames)
		{
			/*
			 * 如果我们在这里找到的列名是空字符串，那么它就是一个
			 * 被删除的列，因此更改为NULL。
			 */
			char	   *fc_cname = strVal(lfirst(fc_lc));

			if (fc_cname[0] == '\0')
				fc_cname = NULL;
			fc_real_colnames[fc_i] = fc_cname;
			fc_i++;
		}
	}

	/*
	 * 确保colinfo->colnames为每一列都有一个槽位。（如果我们已经为最后一列推送了一个名称，它的长度可能已经足够。）注意：
	 * 现在可能有比解析查询时更多的列，即colnames可能比rte->eref->colnames更长。
	 * 我们还必须为新列分配唯一的别名，否则在视图/规则重新加载时可能会出现未解决的冲突。
	 */
	fc_expand_colnames_array_to(fc_colinfo, fc_ncolumns);
	Assert(fc_colinfo->num_cols == fc_ncolumns);

	/*
	 * 还要使new_colnames和is_new_col数组足够大。
	 *
	 * 注意：因为我们在循环之后才将colinfo->num_new_cols设为零，
	 * colname_is_unique不会查询该数组，这很好，因为那只是重复操作。
	 */
	fc_colinfo->new_colnames = (char **) palloc(fc_ncolumns * sizeof(char *));
	fc_colinfo->is_new_col = (bool *) palloc(fc_ncolumns * sizeof(bool));

	/*
	 * 扫描列，为每列选择一个唯一别名，并将其存储在
	 * colinfo->colnames和colinfo->new_colnames中。前者数组对于被删除的列有NULL
	 * 项，后者则省略它们。还要标记
	 * new_colnames中的项是自解析时间以来的新项；对于超出rte->eref->colnames长度的项，就是这种情况。
	 */
	fc_noldcolumns = list_length(fc_rte->eref->colnames);
	fc_changed_any = false;
	fc_j = 0;
	for (fc_i = 0; fc_i < fc_ncolumns; fc_i++)
	{
		char	   *fc_real_colname = fc_real_colnames[fc_i];
		char	   *fc_colname = fc_colinfo->colnames[fc_i];

		/* 跳过被删除的列 */
		if (fc_real_colname == NULL)
		{
			Assert(fc_colname == NULL);	/* colnames[i]已经是NULL */
			continue;
		}

		/* 如果别名已经分配，那就是要使用的 */
		if (fc_colname == NULL)
		{
			/* 如果用户写了一个别名，优先使用它而不是实际列名 */
			if (fc_rte->alias && fc_i < list_length(fc_rte->alias->colnames))
				fc_colname = strVal(list_nth(fc_rte->alias->colnames, fc_i));
			else
				fc_colname = fc_real_colname;

			/* 唯一化并插入到colinfo中 */
			fc_colname = fc_make_colname_unique(fc_colname, fc_dpns, fc_colinfo);

			fc_colinfo->colnames[fc_i] = fc_colname;
		}

		/* 也将非删除列的名称放入new_colnames[]中 */
		fc_colinfo->new_colnames[fc_j] = fc_colname;
		/* 并标记它们是新还是旧 */
		fc_colinfo->is_new_col[fc_j] = (fc_i >= fc_noldcolumns);
		fc_j++;

		/* 记住是否有任何分配的别名与“真实”名称不同 */
		if (!fc_changed_any && strcmp(fc_colname, fc_real_colname) != 0)
			fc_changed_any = true;
	}

	/*
	 * 为new_colnames[]数组设置正确的长度。（注意：如果列已添加，
	 * colinfo->num_cols会包含它们，这并不是真的很准确，但没有害处，因为任何新列必须在最后
	 * ，它们不会影响现有列的varattnos。）
	 */
	fc_colinfo->num_new_cols = fc_j;

	/*
	 * 对于关系RTE，仅当任何别名与底层“真实”名称不同
	 * 时，才打印别名列名。对于函数RTE，始终发出完整的列别名列表；
	 * 这是为了保护可能的默认列名不稳定（例如，来自改变参数名称）。对于tablefunc RTE，我们始终不打印别名，
	 * 因为列名是子句本身的一部分。对于其他RTE类型，
	 * 如果我们更改了任何内容或如果存在用户编写的列别名，
	 * 则打印（因为后者将是底层“现实”的一部分）。
	 */
	if (fc_rte->rtekind == RTE_RELATION)
		fc_colinfo->printaliases = fc_changed_any;
	else if (fc_rte->rtekind == RTE_FUNCTION)
		fc_colinfo->printaliases = true;
	else if (fc_rte->rtekind == RTE_TABLEFUNC)
		fc_colinfo->printaliases = false;
	else if (fc_rte->alias && fc_rte->alias->colnames != NIL)
		fc_colinfo->printaliases = true;
	else
		fc_colinfo->printaliases = fc_changed_any;
}

/*
 * set_join_column_names：为连接RTE选择列别名
 *
 * 列别名信息保存在*colinfo中，假定其已经被零初始化。
 * 如果任何colnames条目已经被填充，则这些将覆盖本地
 * 选择。此外，USING列的名称已经由
 * set_using_names()选择。我们进一步期望输入RTE的列别名选择已经完成。
 */
static void set_join_column_names(deparse_namespace *fc_dpns, RangeTblEntry *fc_rte,
					  deparse_columns *fc_colinfo)
{
	deparse_columns *fc_leftcolinfo;
	deparse_columns *fc_rightcolinfo;
	bool		fc_changed_any;
	int			fc_noldcolumns;
	int			fc_nnewcolumns;
	Bitmapset  *fc_leftmerged = NULL;
	Bitmapset  *fc_rightmerged = NULL;
	int			fc_i;
	int			fc_j;
	int			fc_ic;
	int			fc_jc;

	/* 查找之前填充的child deparse_columns结构 */
	fc_leftcolinfo = deparse_columns_fetch(fc_colinfo->leftrti, fc_dpns);
	fc_rightcolinfo = deparse_columns_fetch(fc_colinfo->rightrti, fc_dpns);

	/*
	 * 确保colinfo->colnames为每一列都有一个槽位。（如果我们已经为最后一列推送了一个名称，它的长度可能已经足够。）注意：
	 * 现在可能有一个或两个输入列的数量超过解析查询时的列，但我们将在下面处理这个问题。
	 * 我们只需要为预先存在的列在colnames中保留条目。
	 */
	fc_noldcolumns = list_length(fc_rte->eref->colnames);
	fc_expand_colnames_array_to(fc_colinfo, fc_noldcolumns);
	Assert(fc_colinfo->num_cols == fc_noldcolumns);

	/*
	 * 扫描连接输出列，为每一列选择一个别名，并将其存储
	 * 在colinfo->colnames中。如果有USING列，set_using_names()
	 * 已经选择了它们的名称，因此我们可以从第一个
	 * 非合并列开始循环。
	 */
	fc_changed_any = false;
	for (fc_i = list_length(fc_colinfo->usingNames); fc_i < fc_noldcolumns; fc_i++)
	{
		char	   *fc_colname = fc_colinfo->colnames[fc_i];
		char	   *fc_real_colname;

		/* 连接列必须引用至少一个输入列 */
		Assert(fc_colinfo->leftattnos[fc_i] != 0 || fc_colinfo->rightattnos[fc_i] != 0);

		/* 获取子列名称 */
		if (fc_colinfo->leftattnos[fc_i] > 0)
			fc_real_colname = fc_leftcolinfo->colnames[fc_colinfo->leftattnos[fc_i] - 1];
		else if (fc_colinfo->rightattnos[fc_i] > 0)
			fc_real_colname = fc_rightcolinfo->colnames[fc_colinfo->rightattnos[fc_i] - 1];
		else
		{
			/* 我们正在连接系统列---使用eref名称 */
			fc_real_colname = strVal(list_nth(fc_rte->eref->colnames, fc_i));
		}

		/* 如果子列已被删除，则无需分配连接列名 */
		if (fc_real_colname == NULL)
		{
			fc_colinfo->colnames[fc_i] = NULL;
			continue;
		}

		/* 在无名连接中，直接报告子列名称即可 */
		if (fc_rte->alias == NULL)
		{
			fc_colinfo->colnames[fc_i] = fc_real_colname;
			continue;
		}

		/* 如果别名已经分配，那就是要使用的 */
		if (fc_colname == NULL)
		{
			/* 如果用户写了一个别名，优先使用它而不是实际列名 */
			if (fc_rte->alias && fc_i < list_length(fc_rte->alias->colnames))
				fc_colname = strVal(list_nth(fc_rte->alias->colnames, fc_i));
			else
				fc_colname = fc_real_colname;

			/* 唯一化并插入到colinfo中 */
			fc_colname = fc_make_colname_unique(fc_colname, fc_dpns, fc_colinfo);

			fc_colinfo->colnames[fc_i] = fc_colname;
		}

		/* 记住是否有任何分配的别名与“真实”名称不同 */
		if (!fc_changed_any && strcmp(fc_colname, fc_real_colname) != 0)
			fc_changed_any = true;
	}

	/*
	 * 计算如果现在重新解析，该连接将拥有多少列，并为
	 * new_colnames和is_new_col数组创建存储。
	 *
	 * 注意：colname_is_unique将在下面的循环中查询new_colnames[]，
	 * 因此其尚未填充的项必须是零。
	 */
	fc_nnewcolumns = fc_leftcolinfo->num_new_cols + fc_rightcolinfo->num_new_cols -
		list_length(fc_colinfo->usingNames);
	fc_colinfo->num_new_cols = fc_nnewcolumns;
	fc_colinfo->new_colnames = (char **) palloc0(fc_nnewcolumns * sizeof(char *));
	fc_colinfo->is_new_col = (bool *) palloc0(fc_nnewcolumns * sizeof(bool));

	
/*
	 * 生成 new_colnames 数组有点棘手，因为在解析时添加的任何新列
	 * 必须插入正确的位置。此代码
	 * 必须与解析器匹配，解析器将连接的列按照合并列优先（按 USING 子句顺序）排序，
	 * 然后是来自左输入的非合并列（按 attnum 顺序），最后是来自右输入的非合并列（同样）。 
	 * 如果其中一个输入本身就是一个连接，它的列将根据相同的规则进行排序，
	 * 这意味着新添加的列可能不在最后。我们可以通过查询 
	 * leftattnos 和 rightattnos 数组以及输入的 is_new_col 数组来弄清楚。
	 *
	 * 在这些循环中，i 索引 leftattnos/rightattnos（所以它是连接的 varattno 
	 * 减去一），j 索引 new_colnames/is_new_col，ic/jc 对当前子 RTE 有类似的含义。
	 */

	/* 处理合并列；它们优先且不能是新的 */
	fc_i = fc_j = 0;
	while (fc_i < fc_noldcolumns &&
		   fc_colinfo->leftattnos[fc_i] != 0 &&
		   fc_colinfo->rightattnos[fc_i] != 0)
	{
		/* 列名已经确定且已知唯一 */
		fc_colinfo->new_colnames[fc_j] = fc_colinfo->colnames[fc_i];
		fc_colinfo->is_new_col[fc_j] = false;

		/* 构建合并列的子 attnums 的位图集 */
		if (fc_colinfo->leftattnos[fc_i] > 0)
			fc_leftmerged = bms_add_member(fc_leftmerged, fc_colinfo->leftattnos[fc_i]);
		if (fc_colinfo->rightattnos[fc_i] > 0)
			fc_rightmerged = bms_add_member(fc_rightmerged, fc_colinfo->rightattnos[fc_i]);

		fc_i++, fc_j++;
	}

	/* 处理非合并的左子列 */
	fc_ic = 0;
	for (fc_jc = 0; fc_jc < fc_leftcolinfo->num_new_cols; fc_jc++)
	{
		char	   *fc_child_colname = fc_leftcolinfo->new_colnames[fc_jc];

		if (!fc_leftcolinfo->is_new_col[fc_jc])
		{
			/* 将 ic 前进到左子输入的下一个未删除的旧列 */
			while (fc_ic < fc_leftcolinfo->num_cols &&
				   fc_leftcolinfo->colnames[fc_ic] == NULL)
				fc_ic++;
			Assert(fc_ic < fc_leftcolinfo->num_cols);
			fc_ic++;
			/* 如果这是一个合并的列，我们已经处理过了 */
			if (bms_is_member(fc_ic, fc_leftmerged))
				continue;
			/* 否则，将 i 移动到相应的现有连接列 */
			while (fc_i < fc_colinfo->num_cols &&
				   fc_colinfo->colnames[fc_i] == NULL)
				fc_i++;
			Assert(fc_i < fc_colinfo->num_cols);
			Assert(fc_ic == fc_colinfo->leftattnos[fc_i]);
			/* 使用该列已分配的名称 */
			fc_colinfo->new_colnames[fc_j] = fc_colinfo->colnames[fc_i];
			fc_i++;
		}
		else
		{
			/*
			 * 唯一化新子列名称并分配，除非我们在一个未命名的连接中，
			 * 在这种情况下只需复制
			 */
			if (fc_rte->alias != NULL)
			{
				fc_colinfo->new_colnames[fc_j] =
					fc_make_colname_unique(fc_child_colname, fc_dpns, fc_colinfo);
				if (!fc_changed_any &&
					strcmp(fc_colinfo->new_colnames[fc_j], fc_child_colname) != 0)
					fc_changed_any = true;
			}
			else
				fc_colinfo->new_colnames[fc_j] = fc_child_colname;
		}

		fc_colinfo->is_new_col[fc_j] = fc_leftcolinfo->is_new_col[fc_jc];
		fc_j++;
	}

	/* 以完全相同的方式处理非合并的右子列 */
	fc_ic = 0;
	for (fc_jc = 0; fc_jc < fc_rightcolinfo->num_new_cols; fc_jc++)
	{
		char	   *fc_child_colname = fc_rightcolinfo->new_colnames[fc_jc];

		if (!fc_rightcolinfo->is_new_col[fc_jc])
		{
			/* 将 ic 移动到下一个未丢弃的旧右子列 */
			while (fc_ic < fc_rightcolinfo->num_cols &&
				   fc_rightcolinfo->colnames[fc_ic] == NULL)
				fc_ic++;
			Assert(fc_ic < fc_rightcolinfo->num_cols);
			fc_ic++;
			/* 如果这是一个合并的列，我们已经处理过了 */
			if (bms_is_member(fc_ic, fc_rightmerged))
				continue;
			/* 否则，将 i 移动到相应的现有连接列 */
			while (fc_i < fc_colinfo->num_cols &&
				   fc_colinfo->colnames[fc_i] == NULL)
				fc_i++;
			Assert(fc_i < fc_colinfo->num_cols);
			Assert(fc_ic == fc_colinfo->rightattnos[fc_i]);
			/* 使用该列已分配的名称 */
			fc_colinfo->new_colnames[fc_j] = fc_colinfo->colnames[fc_i];
			fc_i++;
		}
		else
		{
			/*
			 * 唯一化新子列名称并分配，除非我们在一个未命名的连接中，
			 * 在这种情况下只需复制
			 */
			if (fc_rte->alias != NULL)
			{
				fc_colinfo->new_colnames[fc_j] =
					fc_make_colname_unique(fc_child_colname, fc_dpns, fc_colinfo);
				if (!fc_changed_any &&
					strcmp(fc_colinfo->new_colnames[fc_j], fc_child_colname) != 0)
					fc_changed_any = true;
			}
			else
				fc_colinfo->new_colnames[fc_j] = fc_child_colname;
		}

		fc_colinfo->is_new_col[fc_j] = fc_rightcolinfo->is_new_col[fc_jc];
		fc_j++;
	}

	/* 确认我们处理了正确数量的列 */
#ifdef USE_ASSERT_CHECKING
	while (fc_i < fc_colinfo->num_cols && fc_colinfo->colnames[fc_i] == NULL)
		fc_i++;
	Assert(fc_i == fc_colinfo->num_cols);
	Assert(fc_j == fc_nnewcolumns);
#endif

	/*
	 * 对于命名连接，如果我们更改了子
	 * 名称中的任何内容，则打印列别名。未命名连接无法打印别名。
	 */
	if (fc_rte->alias != NULL)
		fc_colinfo->printaliases = fc_changed_any;
	else
		fc_colinfo->printaliases = false;
}

/*
 * colname_is_unique: colname 是否与已经选择的列名不同？
 *
 * dpns 是查询范围的信息，colinfo 是该列的 RTE 信息
 */
static bool fc_colname_is_unique(const char *fc_colname, deparse_namespace *fc_dpns,
				  deparse_columns *fc_colinfo)
{
	int			fc_i;
	ListCell   *fc_lc;

	/* 检查 RTE 中已分配的列别名 */
	for (fc_i = 0; fc_i < fc_colinfo->num_cols; fc_i++)
	{
		char	   *fc_oldname = fc_colinfo->colnames[fc_i];

		if (fc_oldname && strcmp(fc_oldname, fc_colname) == 0)
			return false;
	}

	/*
	 * 如果我们正在构建新的 colnames 数组，也检查这一点（这将是
	 * 与前面的检查部分但不是完全冗余）
	 */
	for (fc_i = 0; fc_i < fc_colinfo->num_new_cols; fc_i++)
	{
		char	   *fc_oldname = fc_colinfo->new_colnames[fc_i];

		if (fc_oldname && strcmp(fc_oldname, fc_colname) == 0)
			return false;
	}

	/* 还要检查必须全局唯一的 USING 列名 */
	foreach(fc_lc, fc_dpns->using_names)
	{
		char	   *fc_oldname = (char *) lfirst(fc_lc);

		if (strcmp(fc_oldname, fc_colname) == 0)
			return false;
	}

	/* 还要检查已经为父连接的 USING 列分配的名称 */
	foreach(fc_lc, fc_colinfo->parentUsing)
	{
		char	   *fc_oldname = (char *) lfirst(fc_lc);

		if (strcmp(fc_oldname, fc_colname) == 0)
			return false;
	}

	return true;
}

/*
 * make_colname_unique: 如果必要，修改 colname 以使其唯一
 *
 * dpns 是查询范围的信息，colinfo 是该列的 RTE 信息
 */
static char * fc_make_colname_unique(char *fc_colname, deparse_namespace *fc_dpns,
					deparse_columns *fc_colinfo)
{
	/*
	 * 如果所选名称不是唯一的，则附加数字以使其唯一。对于
	 * 非常长的输入名称，我们可能不得不截断以保持在
	 * NAMEDATALEN 范围内。
	 */
	if (!fc_colname_is_unique(fc_colname, fc_dpns, fc_colinfo))
	{
		int			fc_colnamelen = strlen(fc_colname);
		char	   *fc_modname = (char *) palloc(fc_colnamelen + 16);
		int			fc_i = 0;

		do
		{
			fc_i++;
			for (;;)
			{
				memcpy(fc_modname, fc_colname, fc_colnamelen);
				sprintf(fc_modname + fc_colnamelen, "_%d", fc_i);
				if (strlen(fc_modname) < NAMEDATALEN)
					break;
				/* 从 colname 删除字符以保留所有数字 */
				fc_colnamelen = pg_mbcliplen(fc_colname, fc_colnamelen,
										  fc_colnamelen - 1);
			}
		} while (!fc_colname_is_unique(fc_modname, fc_dpns, fc_colinfo));
		fc_colname = fc_modname;
	}
	return fc_colname;
}

/*
 * expand_colnames_array_to: 使 colinfo->colnames 至少有 n 项长
 *
 * 任何添加的数组条目被初始化为零。
 */
static void fc_expand_colnames_array_to(deparse_columns *fc_colinfo, int fc_n)
{
	if (fc_n > fc_colinfo->num_cols)
	{
		if (fc_colinfo->colnames == NULL)
			fc_colinfo->colnames = (char **) palloc0(fc_n * sizeof(char *));
		else
		{
			fc_colinfo->colnames = (char **) repalloc(fc_colinfo->colnames,
												   fc_n * sizeof(char *));
			memset(fc_colinfo->colnames + fc_colinfo->num_cols, 0,
				   (fc_n - fc_colinfo->num_cols) * sizeof(char *));
		}
		fc_colinfo->num_cols = fc_n;
	}
}

/*
 * identify_join_columns: 理清连接的列来自哪里
 *
 * 填充 colinfo 结构的连接特定字段，除了
 * usingNames，这将在后面填充。
 */
static void fc_identify_join_columns(JoinExpr *fc_j, RangeTblEntry *fc_jrte,
					  deparse_columns *fc_colinfo)
{
	int			fc_numjoincols;
	int			fc_jcolno;
	int			fc_rcolno;
	ListCell   *fc_lc;

	/* 提取左/右子 RT 索引 */
	if (IsA(fc_j->larg, RangeTblRef))
		fc_colinfo->leftrti = ((RangeTblRef *) fc_j->larg)->rtindex;
	else if (IsA(fc_j->larg, JoinExpr))
		fc_colinfo->leftrti = ((JoinExpr *) fc_j->larg)->rtindex;
	else
		elog(ERROR, "unrecognized node type in jointree: %d",
			 (int) nodeTag(fc_j->larg));
	if (IsA(fc_j->rarg, RangeTblRef))
		fc_colinfo->rightrti = ((RangeTblRef *) fc_j->rarg)->rtindex;
	else if (IsA(fc_j->rarg, JoinExpr))
		fc_colinfo->rightrti = ((JoinExpr *) fc_j->rarg)->rtindex;
	else
		elog(ERROR, "unrecognized node type in jointree: %d",
			 (int) nodeTag(fc_j->rarg));

	/* 确认子节点将在第二次传递中早于连接处理 */
	Assert(fc_colinfo->leftrti < fc_j->rtindex);
	Assert(fc_colinfo->rightrti < fc_j->rtindex);

	/* 用零初始化结果数组 */
	fc_numjoincols = list_length(fc_jrte->joinaliasvars);
	Assert(fc_numjoincols == list_length(fc_jrte->eref->colnames));
	fc_colinfo->leftattnos = (int *) palloc0(fc_numjoincols * sizeof(int));
	fc_colinfo->rightattnos = (int *) palloc0(fc_numjoincols * sizeof(int));

	/*
	 * 将 RTE 的 joinleftcols/joinrightcols 解构为所需格式。
	 * 回想一下，由于 USING 合并的列是连接输出的第一列。
	 * 在扫描 joinleftcols 时我们无需做任何特殊处理，
	 * 但在扫描 joinrightcols 时必须区分
	 * 合并和未合并的列。
	 */
	fc_jcolno = 0;
	foreach(fc_lc, fc_jrte->joinleftcols)
	{
		int			fc_leftattno = lfirst_int(fc_lc);

		fc_colinfo->leftattnos[fc_jcolno++] = fc_leftattno;
	}
	fc_rcolno = 0;
	foreach(fc_lc, fc_jrte->joinrightcols)
	{
		int			fc_rightattno = lfirst_int(fc_lc);

		if (fc_rcolno < fc_jrte->joinmergedcols)	/* 合并列？ */
			fc_colinfo->rightattnos[fc_rcolno] = fc_rightattno;
		else
			fc_colinfo->rightattnos[fc_jcolno++] = fc_rightattno;
		fc_rcolno++;
	}
	Assert(fc_jcolno == fc_numjoincols);
}

/*
 * get_rtable_name: 获取先前分配的 RTE 别名的便利函数
 *
 * RTE 必须属于“上下文”中的最上层命名空间级别。
 */
static char * get_rtable_name(int fc_rtindex, deparse_context *fc_context)
{
	deparse_namespace *fc_dpns = (deparse_namespace *) linitial(fc_context->namespaces);

	Assert(fc_rtindex > 0 && fc_rtindex <= list_length(fc_dpns->rtable_names));
	return (char *) list_nth(fc_dpns->rtable_names, fc_rtindex - 1);
}

/*
 * set_deparse_plan: 设置 deparse_namespace 以解析给定计划节点的子表达式
 *
 * 这设置了计划、outer_plan、inner_plan、outer_tlist、inner_tlist
 * 和 index_tlist 字段。调用者如果需要，必须已经调整了祖先列表。
 * 请注意，在关注单个计划树中的不同计划节点时，不需要更改 rtable、subplans 和 ctes 字段。
 */
static void set_deparse_plan(deparse_namespace *fc_dpns, Plan *fc_plan)
{
	fc_dpns->plan = fc_plan;

	/*
	 * 我们为 Append 和 MergeAppend 特殊处理，假装第一个子
	 * 计划是 OUTER 参考；我们必须根据其中一个子计划解释 OUTER Vars，
	 * 而第一个是最自然的选择。
	 */
	if (IsA(fc_plan, Append))
		fc_dpns->outer_plan = linitial(((Append *) fc_plan)->appendplans);
	else if (IsA(fc_plan, MergeAppend))
		fc_dpns->outer_plan = linitial(((MergeAppend *) fc_plan)->mergeplans);
	else
		fc_dpns->outer_plan = outerPlan(fc_plan);

	if (fc_dpns->outer_plan)
		fc_dpns->outer_tlist = fc_dpns->outer_plan->targetlist;
	else
		fc_dpns->outer_tlist = NIL;

	/*
	 * 对于 SubqueryScan，假装子计划是 INNER 参考。（我们不
	 * 使用 OUTER，因为这可能将在某一天与正常含义冲突。）
	 * 同样，对于 CteScan，假装子查询的计划是 INNER 参考。
	 * 对于 WorkTableScan，定位父 RecursiveUnion 计划节点并使用
	 * 作为 INNER 参考。
	 *
	 * 对于 MERGE，假装 ModifyTable 的源计划（其外部计划）是
	 * INNER 参考。这是目标关系与数据
	 * 源之间的连接，其他部分的所有 INNER_VAR Vars 都指向其
	 * targetlist。
	 *
	 * 对于 ON CONFLICT .. UPDATE，我们只需要内部 tlist 指向
	 * 被排除表达式的 tlist。（与 SubqueryScan 类似，我们不希望
	 * 重用 OUTER，它在某些修改表的情况下用于 RETURNING，
	 * 尽管不是 INSERT .. CONFLICT）。
	 */
	if (IsA(fc_plan, SubqueryScan))
		fc_dpns->inner_plan = ((SubqueryScan *) fc_plan)->subplan;
	else if (IsA(fc_plan, CteScan))
		fc_dpns->inner_plan = list_nth(fc_dpns->subplans,
									((CteScan *) fc_plan)->ctePlanId - 1);
	else if (IsA(fc_plan, WorkTableScan))
		fc_dpns->inner_plan = fc_find_recursive_union(fc_dpns,
												(WorkTableScan *) fc_plan);
	else if (IsA(fc_plan, ModifyTable))
	{
		if (((ModifyTable *) fc_plan)->operation == CMD_MERGE)
			fc_dpns->inner_plan = outerPlan(fc_plan);
		else
			fc_dpns->inner_plan = fc_plan;
	}
	else
		fc_dpns->inner_plan = innerPlan(fc_plan);

	if (IsA(fc_plan, ModifyTable) && ((ModifyTable *) fc_plan)->operation == CMD_INSERT)
		fc_dpns->inner_tlist = ((ModifyTable *) fc_plan)->exclRelTlist;
	else if (fc_dpns->inner_plan)
		fc_dpns->inner_tlist = fc_dpns->inner_plan->targetlist;
	else
		fc_dpns->inner_tlist = NIL;

	/* 如有需要，为 INDEX_VAR Vars 设置参考 */
	if (IsA(fc_plan, IndexOnlyScan))
		fc_dpns->index_tlist = ((IndexOnlyScan *) fc_plan)->indextlist;
	else if (IsA(fc_plan, ForeignScan))
		fc_dpns->index_tlist = ((ForeignScan *) fc_plan)->fdw_scan_tlist;
	else if (IsA(fc_plan, CustomScan))
		fc_dpns->index_tlist = ((CustomScan *) fc_plan)->custom_scan_tlist;
	else
		fc_dpns->index_tlist = NIL;
}

/*
 * 定位生成 WorkTableScan 的工作表的 RecursiveUnion 的祖先计划节点。
 * 我们可以通过 wtParam 匹配，因为它在计划树中应该是唯一的。
 */
static Plan * fc_find_recursive_union(deparse_namespace *fc_dpns, WorkTableScan *fc_wtscan)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_dpns->ancestors)
	{
		Plan	   *fc_ancestor = (Plan *) lfirst(fc_lc);

		if (IsA(fc_ancestor, RecursiveUnion) &&
			((RecursiveUnion *) fc_ancestor)->wtParam == fc_wtscan->wtParam)
			return fc_ancestor;
	}
	elog(ERROR, "could not find RecursiveUnion for WorkTableScan with wtParam %d",
		 fc_wtscan->wtParam);
	return NULL;
}


static void fc_push_child_plan(deparse_namespace *fc_dpns, Plan *fc_plan,
				deparse_namespace *fc_save_dpns)
{
	/* 保存状态以便后续还原 */
	*fc_save_dpns = *fc_dpns;

	/* 将当前计划节点链接到祖先列表中 */
	fc_dpns->ancestors = lcons(fc_dpns->plan, fc_dpns->ancestors);

	/* 将注意力集中在选定的子节点上 */
	set_deparse_plan(fc_dpns, fc_plan);
}

/*
 * pop_child_plan: 撤销 push_child_plan 的影响
 */
static void fc_pop_child_plan(deparse_namespace *fc_dpns, deparse_namespace *fc_save_dpns)
{
	List	   *fc_ancestors;

	/* 清除由 push_child_plan 添加的祖先列表单元 */
	fc_ancestors = list_delete_first(fc_dpns->ancestors);

	/* 恢复由 push_child_plan 更改的字段 */
	*fc_dpns = *fc_save_dpns;

	/* 确保 dpns->ancestors 是正确的（可能不必要） */
	fc_dpns->ancestors = fc_ancestors;
}

/*
 * push_ancestor_plan: 临时将去解析关注转移到一个
 * 祖先计划
 * 
 * 在扩展 Param 引用时，我们必须调整去解析上下文
 * 以匹配包含被打印表达式的计划节点；
 * 否则，如果该表达式本身包含一个 Param 或
 * OUTER_VAR/INNER_VAR/INDEX_VAR 变量，我们将失败。
 * 
 * 目标祖先通过在 dpns->ancestors 中持有它的 ListCell 方便地识别。
 * 
 * 调用者必须提供一个本地的 deparse_namespace 变量以保存
 * pop_ancestor_plan 的先前状态。
 */
static void fc_push_ancestor_plan(deparse_namespace *fc_dpns, ListCell *fc_ancestor_cell,
				   deparse_namespace *fc_save_dpns)
{
	Plan	   *fc_plan = (Plan *) lfirst(fc_ancestor_cell);

	/* 保存状态以便后续还原 */
	*fc_save_dpns = *fc_dpns;

	/* 构建一个只包含此节点祖先的新祖先列表 */
	fc_dpns->ancestors =
		list_copy_tail(fc_dpns->ancestors,
					   list_cell_number(fc_dpns->ancestors, fc_ancestor_cell) + 1);

	/* 将注意力集中在选定的祖先上 */
	set_deparse_plan(fc_dpns, fc_plan);
}

/*
 * pop_ancestor_plan: 撤销 push_ancestor_plan 的影响
 */
static void fc_pop_ancestor_plan(deparse_namespace *fc_dpns, deparse_namespace *fc_save_dpns)
{
	/* 释放在 push_ancestor_plan 中创建的祖先列表 */
	list_free(fc_dpns->ancestors);

	/* 恢复由 push_ancestor_plan 更改的字段 */
	*fc_dpns = *fc_save_dpns;
}


/* ----------
 * make_ruledef - 重新构建给定 pg_rewrite 元组的 CREATE RULE 命令
 * ----------
 */
static void fc_make_ruledef(StringInfo fc_buf, HeapTuple fc_ruletup, TupleDesc fc_rulettc,
			 int fc_prettyFlags)
{
	char	   *fc_rulename;
	char		fc_ev_type;
	Oid			fc_ev_class;
	bool		fc_is_instead;
	char	   *fc_ev_qual;
	char	   *fc_ev_action;
	List	   *fc_actions;
	Relation	fc_ev_relation;
	TupleDesc	fc_viewResultDesc = NULL;
	int			fc_fno;
	Datum		fc_dat;
	bool		fc_isnull;

	/*
	 * 从规则元组中获取属性值
	 */
	fc_fno = SPI_fnumber(fc_rulettc, "rulename");
	fc_dat = SPI_getbinval(fc_ruletup, fc_rulettc, fc_fno, &fc_isnull);
	Assert(!fc_isnull);
	fc_rulename = NameStr(*(DatumGetName(fc_dat)));

	fc_fno = SPI_fnumber(fc_rulettc, "ev_type");
	fc_dat = SPI_getbinval(fc_ruletup, fc_rulettc, fc_fno, &fc_isnull);
	Assert(!fc_isnull);
	fc_ev_type = DatumGetChar(fc_dat);

	fc_fno = SPI_fnumber(fc_rulettc, "ev_class");
	fc_dat = SPI_getbinval(fc_ruletup, fc_rulettc, fc_fno, &fc_isnull);
	Assert(!fc_isnull);
	fc_ev_class = DatumGetObjectId(fc_dat);

	fc_fno = SPI_fnumber(fc_rulettc, "is_instead");
	fc_dat = SPI_getbinval(fc_ruletup, fc_rulettc, fc_fno, &fc_isnull);
	Assert(!fc_isnull);
	fc_is_instead = DatumGetBool(fc_dat);

	fc_fno = SPI_fnumber(fc_rulettc, "ev_qual");
	fc_ev_qual = SPI_getvalue(fc_ruletup, fc_rulettc, fc_fno);
	Assert(fc_ev_qual != NULL);

	fc_fno = SPI_fnumber(fc_rulettc, "ev_action");
	fc_ev_action = SPI_getvalue(fc_ruletup, fc_rulettc, fc_fno);
	Assert(fc_ev_action != NULL);
	fc_actions = (List *) stringToNode(fc_ev_action);
	if (fc_actions == NIL)
		elog(ERROR, "invalid empty ev_action list");

	fc_ev_relation = table_open(fc_ev_class, AccessShareLock);

	/*
	 * 构建规则定义文本
	 */
	appendStringInfo(fc_buf, "CREATE RULE %s AS",
					 quote_identifier(fc_rulename));

	if (fc_prettyFlags & PRETTYFLAG_INDENT)
		appendStringInfoString(fc_buf, "\n    ON ");
	else
		appendStringInfoString(fc_buf, " ON ");

	/* 规则触发的事件 */
	switch (fc_ev_type)
	{
		case '1':
			appendStringInfoString(fc_buf, "SELECT");
			fc_viewResultDesc = RelationGetDescr(fc_ev_relation);
			break;

		case '2':
			appendStringInfoString(fc_buf, "UPDATE");
			break;

		case '3':
			appendStringInfoString(fc_buf, "INSERT");
			break;

		case '4':
			appendStringInfoString(fc_buf, "DELETE");
			break;

		default:
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("rule \"%s\" has unsupported event type %d",
							fc_rulename, fc_ev_type)));
			break;
	}

	/* 规则所针对的关系 */
	appendStringInfo(fc_buf, " TO %s",
					 (fc_prettyFlags & PRETTYFLAG_SCHEMA) ?
					 fc_generate_relation_name(fc_ev_class, NIL) :
					 fc_generate_qualified_relation_name(fc_ev_class));

	/* 如果规则有事件资格，则添加它 */
	if (strcmp(fc_ev_qual, "<>") != 0)
	{
		Node	   *fc_qual;
		Query	   *fc_query;
		deparse_context fc_context;
		deparse_namespace fc_dpns;

		if (fc_prettyFlags & PRETTYFLAG_INDENT)
			appendStringInfoString(fc_buf, "\n  ");
		appendStringInfoString(fc_buf, " WHERE ");

		fc_qual = stringToNode(fc_ev_qual);

		/*
		 * 我们需要创建一个上下文来识别资格中的任何 Vars
		 * （只能是对 OLD 和 NEW 的引用）。 使用操作列表中
		 * 第一个查询的 rtable 为此目的。
		 */
		fc_query = (Query *) linitial(fc_actions);

		/*
		 * 如果动作是 INSERT...SELECT，OLD/NEW 已经被向下推入
		 * SELECT，这是我们需要查看的内容。（丑陋的权宜
		 * ... 尝试在重新设计查询树时修正这个问题。）
		 */
		fc_query = getInsertSelectQuery(fc_query, NULL);

		/* 必须立即获取锁；请参阅 get_query_def() 中的说明 */
		AcquireRewriteLocks(fc_query, false, false);

		fc_context.buf = fc_buf;
		fc_context.namespaces = list_make1(&fc_dpns);
		fc_context.windowClause = NIL;
		fc_context.windowTList = NIL;
		fc_context.varprefix = (list_length(fc_query->rtable) != 1);
		fc_context.prettyFlags = fc_prettyFlags;
		fc_context.wrapColumn = WRAP_COLUMN_DEFAULT;
		fc_context.indentLevel = PRETTYINDENT_STD;
		fc_context.special_exprkind = EXPR_KIND_NONE;
		fc_context.appendparents = NULL;

		set_deparse_for_query(&fc_dpns, fc_query, NIL);

		get_rule_expr(fc_qual, &fc_context, false);
	}

	appendStringInfoString(fc_buf, " DO ");

	/* INSTEAD 关键字（如果有的话） */
	if (fc_is_instead)
		appendStringInfoString(fc_buf, "INSTEAD ");

	/* 最后的规则动作 */
	if (list_length(fc_actions) > 1)
	{
		ListCell   *fc_action;
		Query	   *fc_query;

		appendStringInfoChar(fc_buf, '(');
		foreach(fc_action, fc_actions)
		{
			fc_query = (Query *) lfirst(fc_action);
			get_query_def(fc_query, fc_buf, NIL, fc_viewResultDesc, true,
						  fc_prettyFlags, WRAP_COLUMN_DEFAULT, 0);
			if (fc_prettyFlags)
				appendStringInfoString(fc_buf, ";\n");
			else
				appendStringInfoString(fc_buf, "; ");
		}
		appendStringInfoString(fc_buf, ");");
	}
	else
	{
		Query	   *fc_query;

		fc_query = (Query *) linitial(fc_actions);
		get_query_def(fc_query, fc_buf, NIL, fc_viewResultDesc, true,
					  fc_prettyFlags, WRAP_COLUMN_DEFAULT, 0);
		appendStringInfoChar(fc_buf, ';');
	}

	table_close(fc_ev_relation, AccessShareLock);
}


/* ----------
 * make_viewdef - 重新构建视图重写规则的 SELECT 部分
 * ----------
 */
static void fc_make_viewdef(StringInfo fc_buf, HeapTuple fc_ruletup, TupleDesc fc_rulettc,
			 int fc_prettyFlags, int fc_wrapColumn)
{
	Query	   *fc_query;
	char		fc_ev_type;
	Oid			fc_ev_class;
	bool		fc_is_instead;
	char	   *fc_ev_qual;
	char	   *fc_ev_action;
	List	   *fc_actions;
	Relation	fc_ev_relation;
	int			fc_fno;
	Datum		fc_dat;
	bool		fc_isnull;

	/*
	 * 从规则元组中获取属性值
	 */
	fc_fno = SPI_fnumber(fc_rulettc, "ev_type");
	fc_dat = SPI_getbinval(fc_ruletup, fc_rulettc, fc_fno, &fc_isnull);
	Assert(!fc_isnull);
	fc_ev_type = DatumGetChar(fc_dat);

	fc_fno = SPI_fnumber(fc_rulettc, "ev_class");
	fc_dat = SPI_getbinval(fc_ruletup, fc_rulettc, fc_fno, &fc_isnull);
	Assert(!fc_isnull);
	fc_ev_class = DatumGetObjectId(fc_dat);

	fc_fno = SPI_fnumber(fc_rulettc, "is_instead");
	fc_dat = SPI_getbinval(fc_ruletup, fc_rulettc, fc_fno, &fc_isnull);
	Assert(!fc_isnull);
	fc_is_instead = DatumGetBool(fc_dat);

	fc_fno = SPI_fnumber(fc_rulettc, "ev_qual");
	fc_ev_qual = SPI_getvalue(fc_ruletup, fc_rulettc, fc_fno);
	Assert(fc_ev_qual != NULL);

	fc_fno = SPI_fnumber(fc_rulettc, "ev_action");
	fc_ev_action = SPI_getvalue(fc_ruletup, fc_rulettc, fc_fno);
	Assert(fc_ev_action != NULL);
	fc_actions = (List *) stringToNode(fc_ev_action);

	if (list_length(fc_actions) != 1)
	{
		/* 保持输出缓冲区为空并离开 */
		return;
	}

	fc_query = (Query *) linitial(fc_actions);

	if (fc_ev_type != '1' || !fc_is_instead ||
		strcmp(fc_ev_qual, "<>") != 0 || fc_query->commandType != CMD_SELECT)
	{
		/* 保持输出缓冲区为空并离开 */
		return;
	}

	fc_ev_relation = table_open(fc_ev_class, AccessShareLock);

	get_query_def(fc_query, fc_buf, NIL, RelationGetDescr(fc_ev_relation), true,
				  fc_prettyFlags, fc_wrapColumn, 0);
	appendStringInfoChar(fc_buf, ';');

	table_close(fc_ev_relation, AccessShareLock);
}


/* ----------
 * get_query_def - 解析一个查询解析树
 *
 * query: 要显示的解析树
 * buf: 输出文本附加到 buf
 * parentnamespace: 外层 deparse_namespace 的列表（最初为空）
 * resultDesc: 如果不为 NULL，则表示由 SELECT 查询表示的视图的输出元组描述符。
 *           我们使用其中的列名来标记 SELECT 输出列，而不是查询中的名称
 * colNamesVisible: 如果周围上下文关心输出
 *           列名（例如，EXISTS() 上下文不关心）；
 *           当为假时，我们可以抑制诸如 "?column?" 的虚拟列标签
 * prettyFlags: PRETTYFLAG_XXX 选项的位掩码
 * wrapColumn: 最大行长度，或 -1 以禁用换行
 * startIndent: 初始缩进量
 * ----------
 */
static void get_query_def(Query *fc_query, StringInfo fc_buf, List *fc_parentnamespace,
			  TupleDesc fc_resultDesc, bool fc_colNamesVisible,
			  int fc_prettyFlags, int fc_wrapColumn, int fc_startIndent)
{
	deparse_context fc_context;
	deparse_namespace fc_dpns;

	/* 防止过长或深度嵌套的查询 */
	CHECK_FOR_INTERRUPTS();
	check_stack_depth();

	/*
	 * 在开始检查查询之前，获取引用关系上的锁，
	 * 并修复 JOIN RTEs 中的删除列。 这确保了
	 * 一致的结果。 注意我们假设可以在传入的
	 * 查询树上进行操作！
	 *
	 * 我们只是去解析查询（我们并不打算执行它），因此我们
	 * 只需要在它提到的关系上获取 AccessShareLock。
	 */
	AcquireRewriteLocks(fc_query, false, false);

	fc_context.buf = fc_buf;
	fc_context.namespaces = lcons(&fc_dpns, list_copy(fc_parentnamespace));
	fc_context.windowClause = NIL;
	fc_context.windowTList = NIL;
	fc_context.varprefix = (fc_parentnamespace != NIL ||
						 list_length(fc_query->rtable) != 1);
	fc_context.prettyFlags = fc_prettyFlags;
	fc_context.wrapColumn = fc_wrapColumn;
	fc_context.indentLevel = fc_startIndent;
	fc_context.special_exprkind = EXPR_KIND_NONE;
	fc_context.appendparents = NULL;

	set_deparse_for_query(&fc_dpns, fc_query, fc_parentnamespace);

	switch (fc_query->commandType)
	{
		case CMD_SELECT:
			get_select_query_def(fc_query, &fc_context, fc_resultDesc, fc_colNamesVisible);
			break;

		case CMD_UPDATE:
			get_update_query_def(fc_query, &fc_context, fc_colNamesVisible);
			break;

		case CMD_INSERT:
			get_insert_query_def(fc_query, &fc_context, fc_colNamesVisible);
			break;

		case CMD_DELETE:
			get_delete_query_def(fc_query, &fc_context, fc_colNamesVisible);
			break;

		case CMD_MERGE:
			get_merge_query_def(fc_query, &fc_context, fc_colNamesVisible);
			break;

		case CMD_NOTHING:
			appendStringInfoString(fc_buf, "NOTHING");
			break;

		case CMD_UTILITY:
			get_utility_query_def(fc_query, &fc_context);
			break;

		default:
			elog(ERROR, "unrecognized query command type: %d",
				 fc_query->commandType);
			break;
	}
}

/* ----------
 * get_values_def - 解析一个 VALUES 列表
 * ----------
 */
static void get_values_def(List *fc_values_lists, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	bool		fc_first_list = true;
	ListCell   *fc_vtl;

	appendStringInfoString(fc_buf, "VALUES ");

	foreach(fc_vtl, fc_values_lists)
	{
		List	   *fc_sublist = (List *) lfirst(fc_vtl);
		bool		fc_first_col = true;
		ListCell   *fc_lc;

		if (fc_first_list)
			fc_first_list = false;
		else
			appendStringInfoString(fc_buf, ", ");

		appendStringInfoChar(fc_buf, '(');
		foreach(fc_lc, fc_sublist)
		{
			Node	   *fc_col = (Node *) lfirst(fc_lc);

			if (fc_first_col)
				fc_first_col = false;
			else
				appendStringInfoChar(fc_buf, ',');

			/*
			 * 打印值。整体行变量需要特殊处理。
			 */
			get_rule_expr_toplevel(fc_col, fc_context, false);
		}
		appendStringInfoChar(fc_buf, ')');
	}
}

/* ----------
 * get_with_clause			- 解析 WITH 子句
 * ----------
 */
static void get_with_clause(Query *fc_query, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	const char *fc_sep;
	ListCell   *fc_l;

	if (fc_query->cteList == NIL)
		return;

	if (PRETTY_INDENT(fc_context))
	{
		fc_context->indentLevel += PRETTYINDENT_STD;
		appendStringInfoChar(fc_buf, ' ');
	}

	if (fc_query->hasRecursive)
		fc_sep = "WITH RECURSIVE ";
	else
		fc_sep = "WITH ";
	foreach(fc_l, fc_query->cteList)
	{
		CommonTableExpr *fc_cte = (CommonTableExpr *) lfirst(fc_l);

		appendStringInfoString(fc_buf, fc_sep);
		appendStringInfoString(fc_buf, quote_identifier(fc_cte->ctename));
		if (fc_cte->aliascolnames)
		{
			bool		fc_first = true;
			ListCell   *fc_col;

			appendStringInfoChar(fc_buf, '(');
			foreach(fc_col, fc_cte->aliascolnames)
			{
				if (fc_first)
					fc_first = false;
				else
					appendStringInfoString(fc_buf, ", ");
				appendStringInfoString(fc_buf,
									   quote_identifier(strVal(lfirst(fc_col))));
			}
			appendStringInfoChar(fc_buf, ')');
		}
		appendStringInfoString(fc_buf, " AS ");
		switch (fc_cte->ctematerialized)
		{
			case CTEMaterializeDefault:
				break;
			case CTEMaterializeAlways:
				appendStringInfoString(fc_buf, "MATERIALIZED ");
				break;
			case CTEMaterializeNever:
				appendStringInfoString(fc_buf, "NOT MATERIALIZED ");
				break;
		}
		appendStringInfoChar(fc_buf, '(');
		if (PRETTY_INDENT(fc_context))
			fc_appendContextKeyword(fc_context, "", 0, 0, 0);
		get_query_def((Query *) fc_cte->ctequery, fc_buf, fc_context->namespaces, NULL,
					  true,
					  fc_context->prettyFlags, fc_context->wrapColumn,
					  fc_context->indentLevel);
		if (PRETTY_INDENT(fc_context))
			fc_appendContextKeyword(fc_context, "", 0, 0, 0);
		appendStringInfoChar(fc_buf, ')');

		if (fc_cte->search_clause)
		{
			bool		fc_first = true;
			ListCell   *fc_lc;

			appendStringInfo(fc_buf, " SEARCH %s FIRST BY ",
							 fc_cte->search_clause->search_breadth_first ? "BREADTH" : "DEPTH");

			foreach(fc_lc, fc_cte->search_clause->search_col_list)
			{
				if (fc_first)
					fc_first = false;
				else
					appendStringInfoString(fc_buf, ", ");
				appendStringInfoString(fc_buf,
									   quote_identifier(strVal(lfirst(fc_lc))));
			}

			appendStringInfo(fc_buf, " SET %s", quote_identifier(fc_cte->search_clause->search_seq_column));
		}

		if (fc_cte->cycle_clause)
		{
			bool		fc_first = true;
			ListCell   *fc_lc;

			appendStringInfoString(fc_buf, " CYCLE ");

			foreach(fc_lc, fc_cte->cycle_clause->cycle_col_list)
			{
				if (fc_first)
					fc_first = false;
				else
					appendStringInfoString(fc_buf, ", ");
				appendStringInfoString(fc_buf,
									   quote_identifier(strVal(lfirst(fc_lc))));
			}

			appendStringInfo(fc_buf, " SET %s", quote_identifier(fc_cte->cycle_clause->cycle_mark_column));

			{
				Const	   *fc_cmv = castNode(Const, fc_cte->cycle_clause->cycle_mark_value);
				Const	   *fc_cmd = castNode(Const, fc_cte->cycle_clause->cycle_mark_default);

				if (!(fc_cmv->consttype == BOOLOID && !fc_cmv->constisnull && DatumGetBool(fc_cmv->constvalue) == true &&
					  fc_cmd->consttype == BOOLOID && !fc_cmd->constisnull && DatumGetBool(fc_cmd->constvalue) == false))
				{
					appendStringInfoString(fc_buf, " TO ");
					get_rule_expr(fc_cte->cycle_clause->cycle_mark_value, fc_context, false);
					appendStringInfoString(fc_buf, " DEFAULT ");
					get_rule_expr(fc_cte->cycle_clause->cycle_mark_default, fc_context, false);
				}
			}

			appendStringInfo(fc_buf, " USING %s", quote_identifier(fc_cte->cycle_clause->cycle_path_column));
		}

		fc_sep = ", ";
	}

	if (PRETTY_INDENT(fc_context))
	{
		fc_context->indentLevel -= PRETTYINDENT_STD;
		fc_appendContextKeyword(fc_context, "", 0, 0, 0);
	}
	else
		appendStringInfoChar(fc_buf, ' ');
}

/* ----------
 * get_select_query_def			- 解析 SELECT 解析树
 * ----------
 */
static void get_select_query_def(Query *fc_query, deparse_context *fc_context,
					 TupleDesc fc_resultDesc, bool fc_colNamesVisible)
{
	StringInfo	fc_buf = fc_context->buf;
	List	   *fc_save_windowclause;
	List	   *fc_save_windowtlist;
	bool		fc_force_colno;
	ListCell   *fc_l;

	/* 如果给出了 WITH 子句，则插入该子句 */
	get_with_clause(fc_query, fc_context);

	/* 设置可能的窗口函数的上下文 */
	fc_save_windowclause = fc_context->windowClause;
	fc_context->windowClause = fc_query->windowClause;
	fc_save_windowtlist = fc_context->windowTList;
	fc_context->windowTList = fc_query->targetList;

	/*
	 * 如果查询节点有 setOperations 树，则这是 UNION/INTERSECT/EXCEPT 查询的顶层；
	 * 仅在顶层查询本身中，WITH、ORDER BY 和 LIMIT 字段是有趣的。
	 */
	if (fc_query->setOperations)
	{
		get_setop_query(fc_query->setOperations, fc_query, fc_context, fc_resultDesc,
						fc_colNamesVisible);
		/* 在这种情况下，ORDER BY 子句必须是简单的 */
		fc_force_colno = true;
	}
	else
	{
		get_basic_select_query(fc_query, fc_context, fc_resultDesc, fc_colNamesVisible);
		fc_force_colno = false;
	}

	/* 如果给出了，添加 ORDER BY 子句 */
	if (fc_query->sortClause != NIL)
	{
		fc_appendContextKeyword(fc_context, " ORDER BY ",
							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
		get_rule_orderby(fc_query->sortClause, fc_query->targetList,
						 fc_force_colno, fc_context);
	}

	/*
	 * 如果给出了，添加 LIMIT/OFFSET 子句。如果是非默认选项，则使用
	 * LIMIT 的标准拼写。
	 */
	if (fc_query->limitOffset != NULL)
	{
		fc_appendContextKeyword(fc_context, " OFFSET ",
							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
		get_rule_expr(fc_query->limitOffset, fc_context, false);
	}
	if (fc_query->limitCount != NULL)
	{
		if (fc_query->limitOption == LIMIT_OPTION_WITH_TIES)
		{
			fc_appendContextKeyword(fc_context, " FETCH FIRST ",
								 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
			get_rule_expr(fc_query->limitCount, fc_context, false);
			appendStringInfoString(fc_buf, " ROWS WITH TIES");
		}
		else
		{
			fc_appendContextKeyword(fc_context, " LIMIT ",
								 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
			if (IsA(fc_query->limitCount, Const) &&
				((Const *) fc_query->limitCount)->constisnull)
				appendStringInfoString(fc_buf, "ALL");
			else
				get_rule_expr(fc_query->limitCount, fc_context, false);
		}
	}

	/* 如果存在，添加 FOR [KEY] UPDATE/SHARE 子句 */
	if (fc_query->hasForUpdate)
	{
		foreach(fc_l, fc_query->rowMarks)
		{
			RowMarkClause *fc_rc = (RowMarkClause *) lfirst(fc_l);

			/* 不打印隐式子句 */
			if (fc_rc->pushedDown)
				continue;

			switch (fc_rc->strength)
			{
				case LCS_NONE:
					/* 我们故意对 LCS_NONE 抛出错误 */
					elog(ERROR, "unrecognized LockClauseStrength %d",
						 (int) fc_rc->strength);
					break;
				case LCS_FORKEYSHARE:
					fc_appendContextKeyword(fc_context, " FOR KEY SHARE",
										 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
					break;
				case LCS_FORSHARE:
					fc_appendContextKeyword(fc_context, " FOR SHARE",
										 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
					break;
				case LCS_FORNOKEYUPDATE:
					fc_appendContextKeyword(fc_context, " FOR NO KEY UPDATE",
										 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
					break;
				case LCS_FORUPDATE:
					fc_appendContextKeyword(fc_context, " FOR UPDATE",
										 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
					break;
			}

			appendStringInfo(fc_buf, " OF %s",
							 quote_identifier(get_rtable_name(fc_rc->rti,
															  fc_context)));
			if (fc_rc->waitPolicy == LockWaitError)
				appendStringInfoString(fc_buf, " NOWAIT");
			else if (fc_rc->waitPolicy == LockWaitSkip)
				appendStringInfoString(fc_buf, " SKIP LOCKED");
		}
	}

	fc_context->windowClause = fc_save_windowclause;
	fc_context->windowTList = fc_save_windowtlist;
}

/*
 * 检测查询是否看起来像 SELECT ... FROM VALUES()，
 * 无需重命名 VALUES RTE 的输出列。
 * 如果是，则返回 VALUES RTE。否则返回 NULL。
 */
static RangeTblEntry * get_simple_values_rte(Query *fc_query, TupleDesc fc_resultDesc)
{
	RangeTblEntry *fc_result = NULL;
	ListCell   *fc_lc;

	/*
	 * 我们想要检测匹配，即使查询还包含 OLD 或 NEW
	 * 规则 RTE。因此，思路是扫描 rtable，看看是否只有
	 * 一个 inFromCl RTE 是 VALUES RTE。
	 */
	foreach(fc_lc, fc_query->rtable)
	{
		RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_lc);

		if (fc_rte->rtekind == RTE_VALUES && fc_rte->inFromCl)
		{
			if (fc_result)
				return NULL;	/* 多个 VALUES (可能不可能) */
			fc_result = fc_rte;
		}
		else if (fc_rte->rtekind == RTE_RELATION && !fc_rte->inFromCl)
			continue;			/* 忽略规则条目 */
		else
			return NULL;		/* 其他情况 -> 不是简单的 VALUES */
	}

	/*
	 * 我们不需要详细检查 targetlist，因为
	 * parser/analyze.c 永远不会生成 “裸” VALUES RTE --- 它们只
	 * 出现在具有非常限制结构的自动生成子查询中。
	 * 但是，DefineView 可能已经通过
	 * 注入新的列别名修改了 tlist，或者我们可能有一些其他列
	 * 别名是由 resultDesc 强制的。我们只有在 RTE 的
	 * 列名与 get_target_list() 将选择的名称匹配时才能进行简化。
	 */
	if (fc_result)
	{
		ListCell   *fc_lcn;
		int			fc_colno;

		if (list_length(fc_query->targetList) != list_length(fc_result->eref->colnames))
			return NULL;		/* 这可能不会发生 */
		fc_colno = 0;
		forboth(fc_lc, fc_query->targetList, fc_lcn, fc_result->eref->colnames)
		{
			TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_lc);
			char	   *fc_cname = strVal(lfirst(fc_lcn));
			char	   *fc_colname;

			if (fc_tle->resjunk)
				return NULL;	/* 这可能不会发生 */

			/* 计算 get_target_list 将为列使用的名称 */
			fc_colno++;
			if (fc_resultDesc && fc_colno <= fc_resultDesc->natts)
				fc_colname = NameStr(TupleDescAttr(fc_resultDesc, fc_colno - 1)->attname);
			else
				fc_colname = fc_tle->resname;

			/* 它与 VALUES RTE 匹配吗？ */
			if (fc_colname == NULL || strcmp(fc_colname, fc_cname) != 0)
				return NULL;	/* 列名已被更改 */
		}
	}

	return fc_result;
}

static void get_basic_select_query(Query *fc_query, deparse_context *fc_context,
					   TupleDesc fc_resultDesc, bool fc_colNamesVisible)
{
	StringInfo	fc_buf = fc_context->buf;
	RangeTblEntry *fc_values_rte;
	char	   *fc_sep;
	ListCell   *fc_l;

	if (PRETTY_INDENT(fc_context))
	{
		fc_context->indentLevel += PRETTYINDENT_STD;
		appendStringInfoChar(fc_buf, ' ');
	}

	/*
	 * 如果查询看起来像 SELECT * FROM (VALUES ...)，则只打印
	 * VALUES 部分。这反转了 transformValuesClause() 在解析
	 * 时所做的。
	 */
	fc_values_rte = get_simple_values_rte(fc_query, fc_resultDesc);
	if (fc_values_rte)
	{
		get_values_def(fc_values_rte->values_lists, fc_context);
		return;
	}

	/*
	 * 构建查询字符串 - 首先我们说 SELECT
	 */
	if (fc_query->isReturn)
		appendStringInfoString(fc_buf, "RETURN");
	else
		appendStringInfoString(fc_buf, "SELECT");

	/* 如果给出了，则添加 DISTINCT 子句 */
	if (fc_query->distinctClause != NIL)
	{
		if (fc_query->hasDistinctOn)
		{
			appendStringInfoString(fc_buf, " DISTINCT ON (");
			fc_sep = "";
			foreach(fc_l, fc_query->distinctClause)
			{
				SortGroupClause *fc_srt = (SortGroupClause *) lfirst(fc_l);

				appendStringInfoString(fc_buf, fc_sep);
				get_rule_sortgroupclause(fc_srt->tleSortGroupRef, fc_query->targetList,
										 false, fc_context);
				fc_sep = ", ";
			}
			appendStringInfoChar(fc_buf, ')');
		}
		else
			appendStringInfoString(fc_buf, " DISTINCT");
	}

	/* 然后我们指明要选择什么（目标列表） */
	get_target_list(fc_query->targetList, fc_context, fc_resultDesc, fc_colNamesVisible);

	/* 如果需要，则添加 FROM 子句 */
	get_from_clause(fc_query, " FROM ", fc_context);

	/* 如果给出了，则添加 WHERE 子句 */
	if (fc_query->jointree->quals != NULL)
	{
		fc_appendContextKeyword(fc_context, " WHERE ",
							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
		get_rule_expr(fc_query->jointree->quals, fc_context, false);
	}

	/* 如果给出了，则添加 GROUP BY 子句 */
	if (fc_query->groupClause != NULL || fc_query->groupingSets != NULL)
	{
		ParseExprKind fc_save_exprkind;

		fc_appendContextKeyword(fc_context, " GROUP BY ",
							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
		if (fc_query->groupDistinct)
			appendStringInfoString(fc_buf, "DISTINCT ");

		fc_save_exprkind = fc_context->special_exprkind;
		fc_context->special_exprkind = EXPR_KIND_GROUP_BY;

		if (fc_query->groupingSets == NIL)
		{
			fc_sep = "";
			foreach(fc_l, fc_query->groupClause)
			{
				SortGroupClause *fc_grp = (SortGroupClause *) lfirst(fc_l);

				appendStringInfoString(fc_buf, fc_sep);
				get_rule_sortgroupclause(fc_grp->tleSortGroupRef, fc_query->targetList,
										 false, fc_context);
				fc_sep = ", ";
			}
		}
		else
		{
			fc_sep = "";
			foreach(fc_l, fc_query->groupingSets)
			{
				GroupingSet *fc_grp = lfirst(fc_l);

				appendStringInfoString(fc_buf, fc_sep);
				get_rule_groupingset(fc_grp, fc_query->targetList, true, fc_context);
				fc_sep = ", ";
			}
		}

		fc_context->special_exprkind = fc_save_exprkind;
	}

	/* 如果给出了，则添加 HAVING 子句 */
	if (fc_query->havingQual != NULL)
	{
		fc_appendContextKeyword(fc_context, " HAVING ",
							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
		get_rule_expr(fc_query->havingQual, fc_context, false);
	}

	/* 如果需要，请添加 WINDOW 子句 */
	if (fc_query->windowClause != NIL)
		get_rule_windowclause(fc_query, fc_context);
}

/* ----------
 * get_target_list - 解析 SELECT 目标列表
 *
 * 这也用于 INSERT/UPDATE/DELETE 的 RETURNING 列表。
 *
 * resultDesc 和 colNamesVisible 的用法与 get_query_def() 相同
 * ----------
 */
static void get_target_list(List *fc_targetList, deparse_context *fc_context,
				TupleDesc fc_resultDesc, bool fc_colNamesVisible)
{
	StringInfo	fc_buf = fc_context->buf;
	StringInfoData fc_targetbuf;
	bool		fc_last_was_multiline = false;
	char	   *fc_sep;
	int			fc_colno;
	ListCell   *fc_l;

	/* 我们使用 targetbuf 暂时保存每个 TLE 的文本 */
	initStringInfo(&fc_targetbuf);

	fc_sep = " ";
	fc_colno = 0;
	foreach(fc_l, fc_targetList)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_l);
		char	   *fc_colname;
		char	   *fc_attname;

		if (fc_tle->resjunk)
			continue;			/* 忽略垃圾条目 */

		appendStringInfoString(fc_buf, fc_sep);
		fc_sep = ", ";
		fc_colno++;

		/*
		 * 将新字段文本放入 targetbuf 中，以便我们可以在获取后
		 * 决定是否需要换行。
		 */
		resetStringInfo(&fc_targetbuf);
		fc_context->buf = &fc_targetbuf;

		/*
		 * 我们对 Var 节点进行特殊处理，而不是使用 get_rule_expr。这是
		 * 必需的，因为 get_rule_expr 会将整行 Var 显示为
		 * "foo.*"，这是在大多数情况下首选的表示法，但在
		 * SELECT 列的顶层，这不正确（解析器会
		 * 将该表示法扩展为多个列，从而产生与整行 Var 不同的行为）。
		 * 我们需要直接调用 get_variable
		 * 以确保它执行正确的操作，并且我们可以获得默认的 AS 标签的属性名称。
		 */
		if (fc_tle->expr && (IsA(fc_tle->expr, Var)))
		{
			fc_attname = get_variable((Var *) fc_tle->expr, 0, true, fc_context);
		}
		else
		{
			get_rule_expr((Node *) fc_tle->expr, fc_context, true);

			/*
			 * 当 colNamesVisible 为 true 时，我们应始终明确显示
			 * 分配的列名。否则，仅在它不是 FigureColname 的后备时显示。
			 */
			fc_attname = fc_colNamesVisible ? NULL : "?column?";
		}

		/*
		 * 找出结果列应该被称为什么。在视图的上下文中
		 * 使用视图的元组描述符（以便提取视图上已进行的
		 * 列重命名的影响）。否则，仅使用我们在 TLE 中找到的内容。
		 */
		if (fc_resultDesc && fc_colno <= fc_resultDesc->natts)
			fc_colname = NameStr(TupleDescAttr(fc_resultDesc, fc_colno - 1)->attname);
		else
			fc_colname = fc_tle->resname;

		/* 显示 AS，除非列的名称本身就是正确的 */
		if (fc_colname)			/* resname 可能为 NULL */
		{
			if (fc_attname == NULL || strcmp(fc_attname, fc_colname) != 0)
				appendStringInfo(&fc_targetbuf, " AS %s", quote_identifier(fc_colname));
		}

		/* 恢复上下文的输出缓冲区 */
		fc_context->buf = fc_buf;

		/* 如果启用则考虑换行 */
		if (PRETTY_INDENT(fc_context) && fc_context->wrapColumn >= 0)
		{
			int			fc_leading_nl_pos;

			/* 新字段是否以新行开头？ */
			if (fc_targetbuf.len > 0 && fc_targetbuf.data[0] == '\n')
				fc_leading_nl_pos = 0;
			else
				fc_leading_nl_pos = -1;

			/* 如果是，我们就不应该添加任何内容 */
			if (fc_leading_nl_pos >= 0)
			{
				/* 相反，删除当前 buf 中的任何尾随空格 */
				fc_removeStringInfoSpaces(fc_buf);
			}
			else
			{
				char	   *fc_trailing_nl;

				/* 在输出缓冲区中定位当前行的开始 */
				fc_trailing_nl = strrchr(fc_buf->data, '\n');
				if (fc_trailing_nl == NULL)
					fc_trailing_nl = fc_buf->data;
				else
					fc_trailing_nl++;

				/*
				 * 如果新字段不是第一个，并且新字段可能导致溢出
				 * 或最后一个字段使用了多于一行，就添加换行和一些缩进。
				 */
				if (fc_colno > 1 &&
					((strlen(fc_trailing_nl) + fc_targetbuf.len > fc_context->wrapColumn) ||
					 fc_last_was_multiline))
					fc_appendContextKeyword(fc_context, "", -PRETTYINDENT_STD,
										 PRETTYINDENT_STD, PRETTYINDENT_VAR);
			}

			/* 记住该字段的多行状态，以便下次迭代使用 */
			fc_last_was_multiline =
				(strchr(fc_targetbuf.data + fc_leading_nl_pos + 1, '\n') != NULL);
		}

		/* 添加新字段 */
		appendBinaryStringInfo(fc_buf, fc_targetbuf.data, fc_targetbuf.len);
	}

	/* 清理 */
	pfree(fc_targetbuf.data);
}

static void get_setop_query(Node *fc_setOp, Query *fc_query, deparse_context *fc_context,
				TupleDesc fc_resultDesc, bool fc_colNamesVisible)
{
	StringInfo	fc_buf = fc_context->buf;
	bool		fc_need_paren;

	/* 防止过长或深度嵌套的查询 */
	CHECK_FOR_INTERRUPTS();
	check_stack_depth();

	if (IsA(fc_setOp, RangeTblRef))
	{
		RangeTblRef *fc_rtr = (RangeTblRef *) fc_setOp;
		RangeTblEntry *fc_rte = rt_fetch(fc_rtr->rtindex, fc_query->rtable);
		Query	   *fc_subquery = fc_rte->subquery;

		Assert(fc_subquery != NULL);

		/*
		 * 如果有 WITH、ORDER BY、FOR UPDATE 或 LIMIT，则需要括号；见 gram.y。
		 * 如果叶查询包含自己的集合操作，也添加括号。
		 * （在没有其他子句存在的情况下，应该不会发生这种情况，见 transformSetOperationTree；
		 * 但为了安全起见，确保加括号。）
		 */
		fc_need_paren = (fc_subquery->cteList ||
					  fc_subquery->sortClause ||
					  fc_subquery->rowMarks ||
					  fc_subquery->limitOffset ||
					  fc_subquery->limitCount ||
					  fc_subquery->setOperations);
		if (fc_need_paren)
			appendStringInfoChar(fc_buf, '(');
		get_query_def(fc_subquery, fc_buf, fc_context->namespaces, fc_resultDesc,
					  fc_colNamesVisible,
					  fc_context->prettyFlags, fc_context->wrapColumn,
					  fc_context->indentLevel);
		if (fc_need_paren)
			appendStringInfoChar(fc_buf, ')');
	}
	else if (IsA(fc_setOp, SetOperationStmt))
	{
		SetOperationStmt *fc_op = (SetOperationStmt *) fc_setOp;
		int			fc_subindent;

		/*
		 * 在嵌套两个 SetOperationStmts 时强制使用括号，除非左侧输入是同类的另一个 setop。
		 * 从语法上讲，我们可以在更多情况下省略括号，但最好用括号标记
		 * setop 操作符更改的情况。如果我们使用括号，也会增加子查询的缩进级别。
		 *
		 * 某些情况下，围绕叶查询也需要括号，但那些在下一级更容易处理（见上面的代码）。
		 */
		if (IsA(fc_op->larg, SetOperationStmt))
		{
			SetOperationStmt *fc_lop = (SetOperationStmt *) fc_op->larg;

			if (fc_op->op == fc_lop->op && fc_op->all == fc_lop->all)
				fc_need_paren = false;
			else
				fc_need_paren = true;
		}
		else
			fc_need_paren = false;

		if (fc_need_paren)
		{
			appendStringInfoChar(fc_buf, '(');
			fc_subindent = PRETTYINDENT_STD;
			fc_appendContextKeyword(fc_context, "", fc_subindent, 0, 0);
		}
		else
			fc_subindent = 0;

		get_setop_query(fc_op->larg, fc_query, fc_context, fc_resultDesc, fc_colNamesVisible);

		if (fc_need_paren)
			fc_appendContextKeyword(fc_context, ") ", -fc_subindent, 0, 0);
		else if (PRETTY_INDENT(fc_context))
			fc_appendContextKeyword(fc_context, "", -fc_subindent, 0, 0);
		else
			appendStringInfoChar(fc_buf, ' ');

		switch (fc_op->op)
		{
			case SETOP_UNION:
				appendStringInfoString(fc_buf, "UNION ");
				break;
			case SETOP_INTERSECT:
				appendStringInfoString(fc_buf, "INTERSECT ");
				break;
			case SETOP_EXCEPT:
				appendStringInfoString(fc_buf, "EXCEPT ");
				break;
			default:
				elog(ERROR, "unrecognized set op: %d",
					 (int) fc_op->op);
		}
		if (fc_op->all)
			appendStringInfoString(fc_buf, "ALL ");

		/* 如果右侧是另一个 setop，则始终加括号 */
		fc_need_paren = IsA(fc_op->rarg, SetOperationStmt);

		/*
		 * 这里的缩进代码故意与左侧输入的代码略有不同，因为我们希望换行
		 * 在不同的位置。
		 */
		if (fc_need_paren)
		{
			appendStringInfoChar(fc_buf, '(');
			fc_subindent = PRETTYINDENT_STD;
		}
		else
			fc_subindent = 0;
		fc_appendContextKeyword(fc_context, "", fc_subindent, 0, 0);

		get_setop_query(fc_op->rarg, fc_query, fc_context, fc_resultDesc, false);

		if (PRETTY_INDENT(fc_context))
			fc_context->indentLevel -= fc_subindent;
		if (fc_need_paren)
			fc_appendContextKeyword(fc_context, ")", 0, 0, 0);
	}
	else
	{
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_setOp));
	}
}

/*
 * 显示排序/分组子句。
 *
 * 还返回表达式树，因此调用者无需再次查找它。
 */
static Node * get_rule_sortgroupclause(Index fc_ref, List *fc_tlist, bool fc_force_colno,
						 deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	TargetEntry *fc_tle;
	Node	   *fc_expr;

	fc_tle = get_sortgroupref_tle(fc_ref, fc_tlist);
	fc_expr = (Node *) fc_tle->expr;

	/*
	 * 如果调用者请求，则使用列号形式。否则，如果
	 * 表达式是常量，则强制它以显式类型转换的形式输出
	 * 作为装饰——这是因为简单的整数常量是
	 * 模棱两可的（如果我们在没有任何装饰的情况下输出它，
	 * findTargetlistEntry() 会错误解释）。如果它是比简单 Var 更复杂的东西，则强制它周围加上额外的括号，以确保不会
	 * 被错误解释为 cube() 或 rollup() 构造。
	 */
	if (fc_force_colno)
	{
		Assert(!fc_tle->resjunk);
		appendStringInfo(fc_buf, "%d", fc_tle->resno);
	}
	else if (fc_expr && IsA(fc_expr, Const))
		get_const_expr((Const *) fc_expr, fc_context, 1);
	else if (!fc_expr || IsA(fc_expr, Var))
		get_rule_expr(fc_expr, fc_context, true);
	else
	{
		/*
		 * 即使 PRETTY_PAREN 关闭，我们也必须强制函数样式的表达式加括号，
		 * 因为这些是易于出现误解析的表达式。对于其他表达式，仅在
		 * PRETTY_PAREN 开启时需要强制加括号，因为否则表达式会自己输出括号（我们无法跳过括号）。 
		 */
		bool		fc_need_paren = (PRETTY_PAREN(fc_context)
								  || IsA(fc_expr, FuncExpr)
								  || IsA(fc_expr, Aggref)
								  || IsA(fc_expr, WindowFunc));

		if (fc_need_paren)
			appendStringInfoChar(fc_context->buf, '(');
		get_rule_expr(fc_expr, fc_context, true);
		if (fc_need_paren)
			appendStringInfoChar(fc_context->buf, ')');
	}

	return fc_expr;
}

/*
 * 显示一个 GroupingSet
 */
static void get_rule_groupingset(GroupingSet *fc_gset, List *fc_targetlist,
					 bool fc_omit_parens, deparse_context *fc_context)
{
	ListCell   *fc_l;
	StringInfo	fc_buf = fc_context->buf;
	bool		fc_omit_child_parens = true;
	char	   *fc_sep = "";

	switch (fc_gset->kind)
	{
		case GROUPING_SET_EMPTY:
			appendStringInfoString(fc_buf, "()");
			return;

		case GROUPING_SET_SIMPLE:
			{
				if (!fc_omit_parens || list_length(fc_gset->content) != 1)
					appendStringInfoChar(fc_buf, '(');

				foreach(fc_l, fc_gset->content)
				{
					Index		fc_ref = lfirst_int(fc_l);

					appendStringInfoString(fc_buf, fc_sep);
					get_rule_sortgroupclause(fc_ref, fc_targetlist,
											 false, fc_context);
					fc_sep = ", ";
				}

				if (!fc_omit_parens || list_length(fc_gset->content) != 1)
					appendStringInfoChar(fc_buf, ')');
			}
			return;

		case GROUPING_SET_ROLLUP:
			appendStringInfoString(fc_buf, "ROLLUP(");
			break;
		case GROUPING_SET_CUBE:
			appendStringInfoString(fc_buf, "CUBE(");
			break;
		case GROUPING_SET_SETS:
			appendStringInfoString(fc_buf, "GROUPING SETS (");
			fc_omit_child_parens = false;
			break;
	}

	foreach(fc_l, fc_gset->content)
	{
		appendStringInfoString(fc_buf, fc_sep);
		get_rule_groupingset(lfirst(fc_l), fc_targetlist, fc_omit_child_parens, fc_context);
		fc_sep = ", ";
	}

	appendStringInfoChar(fc_buf, ')');
}

/*
 * 显示 ORDER BY 列表。
 */
static void get_rule_orderby(List *fc_orderList, List *fc_targetList,
				 bool fc_force_colno, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	const char *fc_sep;
	ListCell   *fc_l;

	fc_sep = "";
	foreach(fc_l, fc_orderList)
	{
		SortGroupClause *fc_srt = (SortGroupClause *) lfirst(fc_l);
		Node	   *fc_sortexpr;
		Oid			fc_sortcoltype;
		TypeCacheEntry *fc_typentry;

		appendStringInfoString(fc_buf, fc_sep);
		fc_sortexpr = get_rule_sortgroupclause(fc_srt->tleSortGroupRef, fc_targetList,
											fc_force_colno, fc_context);
		fc_sortcoltype = exprType(fc_sortexpr);
		/* 查看运算符是否是该数据类型的默认 < 或 > */
		fc_typentry = lookup_type_cache(fc_sortcoltype,
									 TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
		if (fc_srt->sortop == fc_typentry->lt_opr)
		{
			/* ASC 是默认值，因此对此不发出任何声明 */
			if (fc_srt->nulls_first)
				appendStringInfoString(fc_buf, " NULLS FIRST");
		}
		else if (fc_srt->sortop == fc_typentry->gt_opr)
		{
			appendStringInfoString(fc_buf, " DESC");
			/* DESC 默认设置为 NULLS FIRST */
			if (!fc_srt->nulls_first)
				appendStringInfoString(fc_buf, " NULLS LAST");
		}
		else
		{
			appendStringInfo(fc_buf, " USING %s",
							 fc_generate_operator_name(fc_srt->sortop,
													fc_sortcoltype,
													fc_sortcoltype));
			/* 具体说明以消除歧义 */
			if (fc_srt->nulls_first)
				appendStringInfoString(fc_buf, " NULLS FIRST");
			else
				appendStringInfoString(fc_buf, " NULLS LAST");
		}
		fc_sep = ", ";
	}
}

/* 
 * 显示一个 WINDOW 子句。
 *
 * 注意，windowClause 列表可能仅包含匿名窗口
 * 规范，在这种情况下，我们这里不应该打印任何内容。
 */
static void get_rule_windowclause(Query *fc_query, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	const char *fc_sep;
	ListCell   *fc_l;

	fc_sep = NULL;
	foreach(fc_l, fc_query->windowClause)
	{
		WindowClause *fc_wc = (WindowClause *) lfirst(fc_l);

		if (fc_wc->name == NULL)
			continue;			/* 忽略匿名窗口 */

		if (fc_sep == NULL)
			fc_appendContextKeyword(fc_context, " WINDOW ",
								 -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
		else
			appendStringInfoString(fc_buf, fc_sep);

		appendStringInfo(fc_buf, "%s AS ", quote_identifier(fc_wc->name));

		get_rule_windowspec(fc_wc, fc_query->targetList, fc_context);

		fc_sep = ", ";
	}
}

/* 
 * 显示一个窗口定义
 */
static void get_rule_windowspec(WindowClause *fc_wc, List *fc_targetList,
					deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	bool		fc_needspace = false;
	const char *fc_sep;
	ListCell   *fc_l;

	appendStringInfoChar(fc_buf, '(');
	if (fc_wc->refname)
	{
		appendStringInfoString(fc_buf, quote_identifier(fc_wc->refname));
		fc_needspace = true;
	}
	/* 分区子句总是被继承，所以只有在没有 refname 时才打印 */
	if (fc_wc->partitionClause && !fc_wc->refname)
	{
		if (fc_needspace)
			appendStringInfoChar(fc_buf, ' ');
		appendStringInfoString(fc_buf, "PARTITION BY ");
		fc_sep = "";
		foreach(fc_l, fc_wc->partitionClause)
		{
			SortGroupClause *fc_grp = (SortGroupClause *) lfirst(fc_l);

			appendStringInfoString(fc_buf, fc_sep);
			get_rule_sortgroupclause(fc_grp->tleSortGroupRef, fc_targetList,
									 false, fc_context);
			fc_sep = ", ";
		}
		fc_needspace = true;
	}
	/* 仅在未继承的情况下打印排序子句 */
	if (fc_wc->orderClause && !fc_wc->copiedOrder)
	{
		if (fc_needspace)
			appendStringInfoChar(fc_buf, ' ');
		appendStringInfoString(fc_buf, "ORDER BY ");
		get_rule_orderby(fc_wc->orderClause, fc_targetList, false, fc_context);
		fc_needspace = true;
	}
	/* 框架子句从不被继承，因此除非是默认值，否则要打印 */
	if (fc_wc->frameOptions & FRAMEOPTION_NONDEFAULT)
	{
		if (fc_needspace)
			appendStringInfoChar(fc_buf, ' ');
		if (fc_wc->frameOptions & FRAMEOPTION_RANGE)
			appendStringInfoString(fc_buf, "RANGE ");
		else if (fc_wc->frameOptions & FRAMEOPTION_ROWS)
			appendStringInfoString(fc_buf, "ROWS ");
		else if (fc_wc->frameOptions & FRAMEOPTION_GROUPS)
			appendStringInfoString(fc_buf, "GROUPS ");
		else
			Assert(false);
		if (fc_wc->frameOptions & FRAMEOPTION_BETWEEN)
			appendStringInfoString(fc_buf, "BETWEEN ");
		if (fc_wc->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
			appendStringInfoString(fc_buf, "UNBOUNDED PRECEDING ");
		else if (fc_wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW)
			appendStringInfoString(fc_buf, "CURRENT ROW ");
		else if (fc_wc->frameOptions & FRAMEOPTION_START_OFFSET)
		{
			get_rule_expr(fc_wc->startOffset, fc_context, false);
			if (fc_wc->frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
				appendStringInfoString(fc_buf, " PRECEDING ");
			else if (fc_wc->frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING)
				appendStringInfoString(fc_buf, " FOLLOWING ");
			else
				Assert(false);
		}
		else
			Assert(false);
		if (fc_wc->frameOptions & FRAMEOPTION_BETWEEN)
		{
			appendStringInfoString(fc_buf, "AND ");
			if (fc_wc->frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
				appendStringInfoString(fc_buf, "UNBOUNDED FOLLOWING ");
			else if (fc_wc->frameOptions & FRAMEOPTION_END_CURRENT_ROW)
				appendStringInfoString(fc_buf, "CURRENT ROW ");
			else if (fc_wc->frameOptions & FRAMEOPTION_END_OFFSET)
			{
				get_rule_expr(fc_wc->endOffset, fc_context, false);
				if (fc_wc->frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
					appendStringInfoString(fc_buf, " PRECEDING ");
				else if (fc_wc->frameOptions & FRAMEOPTION_END_OFFSET_FOLLOWING)
					appendStringInfoString(fc_buf, " FOLLOWING ");
				else
					Assert(false);
			}
			else
				Assert(false);
		}
		if (fc_wc->frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW)
			appendStringInfoString(fc_buf, "EXCLUDE CURRENT ROW ");
		else if (fc_wc->frameOptions & FRAMEOPTION_EXCLUDE_GROUP)
			appendStringInfoString(fc_buf, "EXCLUDE GROUP ");
		else if (fc_wc->frameOptions & FRAMEOPTION_EXCLUDE_TIES)
			appendStringInfoString(fc_buf, "EXCLUDE TIES ");
		/* 我们现在将有一个尾随空格；去除它 */
		fc_buf->len--;
	}
	appendStringInfoChar(fc_buf, ')');
}

/* ---------- 
 * get_insert_query_def - 反解析 INSERT parsetree 
 * ----------
 */
static void get_insert_query_def(Query *fc_query, deparse_context *fc_context,
					 bool fc_colNamesVisible)
{
	StringInfo	fc_buf = fc_context->buf;
	RangeTblEntry *fc_select_rte = NULL;
	RangeTblEntry *fc_values_rte = NULL;
	RangeTblEntry *fc_rte;
	char	   *fc_sep;
	ListCell   *fc_l;
	List	   *fc_strippedexprs;

	/* 如果给出了 WITH 子句，则插入该子句 */
	get_with_clause(fc_query, fc_context);

	/* 
 * 如果是 INSERT ... SELECT 或多行 VALUES，将会有一个
 * 单一的 RTE 用于 SELECT 或 VALUES。普通的 VALUES 既没有。
 */
	foreach(fc_l, fc_query->rtable)
	{
		fc_rte = (RangeTblEntry *) lfirst(fc_l);

		if (fc_rte->rtekind == RTE_SUBQUERY)
		{
			if (fc_select_rte)
				elog(ERROR, "too many subquery RTEs in INSERT");
			fc_select_rte = fc_rte;
		}

		if (fc_rte->rtekind == RTE_VALUES)
		{
			if (fc_values_rte)
				elog(ERROR, "too many values RTEs in INSERT");
			fc_values_rte = fc_rte;
		}
	}
	if (fc_select_rte && fc_values_rte)
		elog(ERROR, "both subquery and values RTEs in INSERT");

	/* 
 * 从 INSERT INTO relname 开始查询
 */
	fc_rte = rt_fetch(fc_query->resultRelation, fc_query->rtable);
	Assert(fc_rte->rtekind == RTE_RELATION);

	if (PRETTY_INDENT(fc_context))
	{
		fc_context->indentLevel += PRETTYINDENT_STD;
		appendStringInfoChar(fc_buf, ' ');
	}
	appendStringInfo(fc_buf, "INSERT INTO %s",
					 fc_generate_relation_name(fc_rte->relid, NIL));

	/* 如果需要，打印关系别名；INSERT 需要显式的 AS */
	get_rte_alias(fc_rte, fc_query->resultRelation, true, fc_context);

	/* 在这里总是想要一个空格 */
	appendStringInfoChar(fc_buf, ' ');

	/* 
 * 添加插入列名列表。任何需要的间接装饰可以从
 * 顶级 targetlist 推断出来。
 */
	fc_strippedexprs = NIL;
	fc_sep = "";
	if (fc_query->targetList)
		appendStringInfoChar(fc_buf, '(');
	foreach(fc_l, fc_query->targetList)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_l);

		if (fc_tle->resjunk)
			continue;			/* 忽略垃圾条目 */

		appendStringInfoString(fc_buf, fc_sep);
		fc_sep = ", ";

		/* 
 * 输出目标列的名称；查看目录，而不是 tle->resname，
 * 因为 resname 无法跟踪 RENAME。
 */
		appendStringInfoString(fc_buf,
							   quote_identifier(get_attname(fc_rte->relid,
															fc_tle->resno,
															false)));

		/* 
 * 打印任何需要的间接（子字段或下标），并去掉
 * 代表间接赋值的顶级节点。
 * 将剥离的表达式添加到 strippedexprs 中。 （如果是
 * 单行 VALUES 语句，剥离的表达式就是下面要打印的 VALUES。
 * 否则它们只是 Vars，并不真正
 * 有趣。）
 */
		fc_strippedexprs = lappend(fc_strippedexprs,
								fc_processIndirection((Node *) fc_tle->expr,
												   fc_context));
	}
	if (fc_query->targetList)
		appendStringInfoString(fc_buf, ") ");

	if (fc_query->override)
	{
		if (fc_query->override == OVERRIDING_SYSTEM_VALUE)
			appendStringInfoString(fc_buf, "OVERRIDING SYSTEM VALUE ");
		else if (fc_query->override == OVERRIDING_USER_VALUE)
			appendStringInfoString(fc_buf, "OVERRIDING USER VALUE ");
	}

	if (fc_select_rte)
	{
		/* 添加 SELECT */
		get_query_def(fc_select_rte->subquery, fc_buf, fc_context->namespaces, NULL,
					  false,
					  fc_context->prettyFlags, fc_context->wrapColumn,
					  fc_context->indentLevel);
	}
	else if (fc_values_rte)
	{
		/* 添加多行 VALUES 表达式列表 */
		get_values_def(fc_values_rte->values_lists, fc_context);
	}
	else if (fc_strippedexprs)
	{
		/* 添加单行 VALUES 表达式列表 */
		fc_appendContextKeyword(fc_context, "VALUES (",
							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
		get_rule_list_toplevel(fc_strippedexprs, fc_context, false);
		appendStringInfoChar(fc_buf, ')');
	}
	else
	{
		/* 没有表达式，所以它必须是 DEFAULT VALUES */
		appendStringInfoString(fc_buf, "DEFAULT VALUES");
	}

	/* 添加 ON CONFLICT（如果存在） */
	if (fc_query->onConflict)
	{
		OnConflictExpr *fc_confl = fc_query->onConflict;

		appendStringInfoString(fc_buf, " ON CONFLICT");

		if (fc_confl->arbiterElems)
		{
			/* 添加单行 VALUES 表达式列表 */
			appendStringInfoChar(fc_buf, '(');
			get_rule_expr((Node *) fc_confl->arbiterElems, fc_context, false);
			appendStringInfoChar(fc_buf, ')');

			/* 如果给出，添加 WHERE 子句（用于部分索引） */
			if (fc_confl->arbiterWhere != NULL)
			{
				bool		fc_save_varprefix;

				/* 
 * 强制非前缀化 Vars，因为解析器假定它们
 * 属于目标关系。 WHERE 子句不使用
 * InferenceElem，因此这是单独要求的。
 */
				fc_save_varprefix = fc_context->varprefix;
				fc_context->varprefix = false;

				fc_appendContextKeyword(fc_context, " WHERE ",
									 -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
				get_rule_expr(fc_confl->arbiterWhere, fc_context, false);

				fc_context->varprefix = fc_save_varprefix;
			}
		}
		else if (OidIsValid(fc_confl->constraint))
		{
			char	   *fc_constraint = get_constraint_name(fc_confl->constraint);

			if (!fc_constraint)
				elog(ERROR, "cache lookup failed for constraint %u",
					 fc_confl->constraint);
			appendStringInfo(fc_buf, " ON CONSTRAINT %s",
							 quote_identifier(fc_constraint));
		}

		if (fc_confl->action == ONCONFLICT_NOTHING)
		{
			appendStringInfoString(fc_buf, " DO NOTHING");
		}
		else
		{
			appendStringInfoString(fc_buf, " DO UPDATE SET ");
			/* 反解析 targetlist */
			get_update_query_targetlist_def(fc_query, fc_confl->onConflictSet,
											fc_context, fc_rte);

			/* 如果给出，添加 WHERE 子句 */
			if (fc_confl->onConflictWhere != NULL)
			{
				fc_appendContextKeyword(fc_context, " WHERE ",
									 -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
				get_rule_expr(fc_confl->onConflictWhere, fc_context, false);
			}
		}
	}

	/* 如果存在，添加 RETURNING */
	if (fc_query->returningList)
	{
		fc_appendContextKeyword(fc_context, " RETURNING",
							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
		get_target_list(fc_query->returningList, fc_context, NULL, fc_colNamesVisible);
	}
}


/* ---------- 
 * get_update_query_def - 反解析 UPDATE parsetree 
 * ----------
 */
static void get_update_query_def(Query *fc_query, deparse_context *fc_context,
					 bool fc_colNamesVisible)
{
	StringInfo	fc_buf = fc_context->buf;
	RangeTblEntry *fc_rte;

	/* 如果给出了 WITH 子句，则插入该子句 */
	get_with_clause(fc_query, fc_context);

	/* 
 * 从 UPDATE relname SET 开始查询
 */
	fc_rte = rt_fetch(fc_query->resultRelation, fc_query->rtable);
	Assert(fc_rte->rtekind == RTE_RELATION);
	if (PRETTY_INDENT(fc_context))
	{
		appendStringInfoChar(fc_buf, ' ');
		fc_context->indentLevel += PRETTYINDENT_STD;
	}
	appendStringInfo(fc_buf, "UPDATE %s%s",
					 only_marker(fc_rte),
					 fc_generate_relation_name(fc_rte->relid, NIL));

	/* 如果需要，打印关系别名 */
	get_rte_alias(fc_rte, fc_query->resultRelation, false, fc_context);

	appendStringInfoString(fc_buf, " SET ");

	/* 反解析 targetlist */
	get_update_query_targetlist_def(fc_query, fc_query->targetList, fc_context, fc_rte);

	/* 如果需要，则添加 FROM 子句 */
	get_from_clause(fc_query, " FROM ", fc_context);

	/* 如果给出，添加 WHERE 子句 */
	if (fc_query->jointree->quals != NULL)
	{
		fc_appendContextKeyword(fc_context, " WHERE ",
							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
		get_rule_expr(fc_query->jointree->quals, fc_context, false);
	}

	/* 如果存在，添加 RETURNING */
	if (fc_query->returningList)
	{
		fc_appendContextKeyword(fc_context, " RETURNING",
							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
		get_target_list(fc_query->returningList, fc_context, NULL, fc_colNamesVisible);
	}
}


/* ----------
 * get_update_query_targetlist_def			- 解析 UPDATE 目标列表
 * ----------
 */
static void get_update_query_targetlist_def(Query *fc_query, List *fc_targetList,
								deparse_context *fc_context, RangeTblEntry *fc_rte)
{
	StringInfo	fc_buf = fc_context->buf;
	ListCell   *fc_l;
	ListCell   *fc_next_ma_cell;
	int			fc_remaining_ma_columns;
	const char *fc_sep;
	SubLink    *fc_cur_ma_sublink;
	List	   *fc_ma_sublinks;

	/*
	 * 准备处理 MULTIEXPR 赋值：将源 SubLinks 收集到一个列表中。
	 * 我们期望它们在 resjunk tlist 条目中按 ID 顺序出现。
	 */
	fc_ma_sublinks = NIL;
	if (fc_query->hasSubLinks)		/* 否则就不可能有任何 */
	{
		foreach(fc_l, fc_targetList)
		{
			TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_l);

			if (fc_tle->resjunk && IsA(fc_tle->expr, SubLink))
			{
				SubLink    *fc_sl = (SubLink *) fc_tle->expr;

				if (fc_sl->subLinkType == MULTIEXPR_SUBLINK)
				{
					fc_ma_sublinks = lappend(fc_ma_sublinks, fc_sl);
					Assert(fc_sl->subLinkId == list_length(fc_ma_sublinks));
				}
			}
		}
	}
	fc_next_ma_cell = list_head(fc_ma_sublinks);
	fc_cur_ma_sublink = NULL;
	fc_remaining_ma_columns = 0;

	/* 添加以逗号分隔的 'attname = value' 列表 */
	fc_sep = "";
	foreach(fc_l, fc_targetList)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_l);
		Node	   *fc_expr;

		if (fc_tle->resjunk)
			continue;			/* 忽略垃圾条目 */

		/* 发出分隔符（无论我们是否在多重赋值中都可以） */
		appendStringInfoString(fc_buf, fc_sep);
		fc_sep = ", ";

		/*
		 * 检查我们是否正在开始多重赋值组：如果是，
		 * 则输出左括号。
		 */
		if (fc_next_ma_cell != NULL && fc_cur_ma_sublink == NULL)
		{
			/*
			 * 我们必须深入到表达式中以查看它是否是 PARAM_MULTIEXPR
			 * 参数。那可能被 FieldStores 和
			 * SubscriptingRefs 和 CoerceToDomains（参见 processIndirection()）掩盖，
			 * 而在这些之下可能还有一个隐式类型转换。
			 * 因为我们无论如何都会忽略隐式类型转换，所以我们
			 * 不需要像 processIndirection() 那样小心对待
			 * 通过隐式 CoerceToDomains 下降。
			 */
			fc_expr = (Node *) fc_tle->expr;
			while (fc_expr)
			{
				if (IsA(fc_expr, FieldStore))
				{
					FieldStore *fc_fstore = (FieldStore *) fc_expr;

					fc_expr = (Node *) linitial(fc_fstore->newvals);
				}
				else if (IsA(fc_expr, SubscriptingRef))
				{
					SubscriptingRef *fc_sbsref = (SubscriptingRef *) fc_expr;

					if (fc_sbsref->refassgnexpr == NULL)
						break;

					fc_expr = (Node *) fc_sbsref->refassgnexpr;
				}
				else if (IsA(fc_expr, CoerceToDomain))
				{
					CoerceToDomain *fc_cdomain = (CoerceToDomain *) fc_expr;

					if (fc_cdomain->coercionformat != COERCE_IMPLICIT_CAST)
						break;
					fc_expr = (Node *) fc_cdomain->arg;
				}
				else
					break;
			}
			fc_expr = strip_implicit_coercions(fc_expr);

			if (fc_expr && IsA(fc_expr, Param) &&
				((Param *) fc_expr)->paramkind == PARAM_MULTIEXPR)
			{
				fc_cur_ma_sublink = (SubLink *) lfirst(fc_next_ma_cell);
				fc_next_ma_cell = lnext(fc_ma_sublinks, fc_next_ma_cell);
				fc_remaining_ma_columns = count_nonjunk_tlist_entries(((Query *) fc_cur_ma_sublink->subselect)->targetList);
				Assert(((Param *) fc_expr)->paramid ==
					   ((fc_cur_ma_sublink->subLinkId << 16) | 1));
				appendStringInfoChar(fc_buf, '(');
			}
		}

		/* 
 * 输出目标列的名称；查看目录，而不是 tle->resname，
 * 因为 resname 无法跟踪 RENAME。
 */
		appendStringInfoString(fc_buf,
							   quote_identifier(get_attname(fc_rte->relid,
															fc_tle->resno,
															false)));

		/*
		 * 打印任何所需的间接引用（子字段或子脚本），并去掉
		 * 代表间接赋值的顶层节点。
		 */
		fc_expr = fc_processIndirection((Node *) fc_tle->expr, fc_context);

		/*
		 * 如果我们处于多重赋值中，跳过打印更多内容，除非
		 * 这是最后一列；在这种情况下，我们打印的内容应该是
		 * sublink，而不是参数。
		 */
		if (fc_cur_ma_sublink != NULL)
		{
			if (--fc_remaining_ma_columns > 0)
				continue;		/* 不是多重赋值的最后一列 */
			appendStringInfoChar(fc_buf, ')');
			fc_expr = (Node *) fc_cur_ma_sublink;
			fc_cur_ma_sublink = NULL;
		}

		appendStringInfoString(fc_buf, " = ");

		get_rule_expr(fc_expr, fc_context, false);
	}
}


/* ----------
 * get_delete_query_def			- 解析 DELETE 解析树
 * ----------
 */
static void get_delete_query_def(Query *fc_query, deparse_context *fc_context,
					 bool fc_colNamesVisible)
{
	StringInfo	fc_buf = fc_context->buf;
	RangeTblEntry *fc_rte;

	/* 如果给出了 WITH 子句，则插入该子句 */
	get_with_clause(fc_query, fc_context);

	/*
	 * 以 DELETE FROM relname 开始查询
	 */
	fc_rte = rt_fetch(fc_query->resultRelation, fc_query->rtable);
	Assert(fc_rte->rtekind == RTE_RELATION);
	if (PRETTY_INDENT(fc_context))
	{
		appendStringInfoChar(fc_buf, ' ');
		fc_context->indentLevel += PRETTYINDENT_STD;
	}
	appendStringInfo(fc_buf, "DELETE FROM %s%s",
					 only_marker(fc_rte),
					 fc_generate_relation_name(fc_rte->relid, NIL));

	/* 如果需要，打印关系别名 */
	get_rte_alias(fc_rte, fc_query->resultRelation, false, fc_context);

	/* 如果给定，则添加 USING 子句 */
	get_from_clause(fc_query, " USING ", fc_context);

	/* 如果给出，添加 WHERE 子句 */
	if (fc_query->jointree->quals != NULL)
	{
		fc_appendContextKeyword(fc_context, " WHERE ",
							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
		get_rule_expr(fc_query->jointree->quals, fc_context, false);
	}

	/* 如果存在，添加 RETURNING */
	if (fc_query->returningList)
	{
		fc_appendContextKeyword(fc_context, " RETURNING",
							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
		get_target_list(fc_query->returningList, fc_context, NULL, fc_colNamesVisible);
	}
}


/* ----------
 * get_merge_query_def				- 解析 MERGE 解析树
 * ----------
 */
static void get_merge_query_def(Query *fc_query, deparse_context *fc_context,
					bool fc_colNamesVisible)
{
	StringInfo	fc_buf = fc_context->buf;
	RangeTblEntry *fc_rte;
	ListCell   *fc_lc;

	/* 如果给出了 WITH 子句，则插入该子句 */
	get_with_clause(fc_query, fc_context);

	/*
	 * 以 MERGE INTO relname 开始查询
	 */
	fc_rte = rt_fetch(fc_query->resultRelation, fc_query->rtable);
	Assert(fc_rte->rtekind == RTE_RELATION);
	if (PRETTY_INDENT(fc_context))
	{
		appendStringInfoChar(fc_buf, ' ');
		fc_context->indentLevel += PRETTYINDENT_STD;
	}
	appendStringInfo(fc_buf, "MERGE INTO %s%s",
					 only_marker(fc_rte),
					 fc_generate_relation_name(fc_rte->relid, NIL));

	/* 如果需要，打印关系别名 */
	get_rte_alias(fc_rte, fc_query->resultRelation, false, fc_context);

	/* 打印源关系和连接子句 */
	get_from_clause(fc_query, " USING ", fc_context);
	fc_appendContextKeyword(fc_context, " ON ",
						 -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
	get_rule_expr(fc_query->jointree->quals, fc_context, false);

	/* 打印每个合并操作 */
	foreach(fc_lc, fc_query->mergeActionList)
	{
		MergeAction *fc_action = lfirst_node(MergeAction, fc_lc);

		fc_appendContextKeyword(fc_context, " WHEN ",
							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
		appendStringInfo(fc_buf, "%sMATCHED", fc_action->matched ? "" : "NOT ");

		if (fc_action->qual)
		{
			fc_appendContextKeyword(fc_context, " AND ",
								 -PRETTYINDENT_STD, PRETTYINDENT_STD, 3);
			get_rule_expr(fc_action->qual, fc_context, false);
		}
		fc_appendContextKeyword(fc_context, " THEN ",
							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 3);

		if (fc_action->commandType == CMD_INSERT)
		{
			/* 这通常匹配 get_insert_query_def() */
			List	   *fc_strippedexprs = NIL;
			const char *fc_sep = "";
			ListCell   *fc_lc2;

			appendStringInfoString(fc_buf, "INSERT");

			if (fc_action->targetList)
				appendStringInfoString(fc_buf, " (");
			foreach(fc_lc2, fc_action->targetList)
			{
				TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_lc2);

				Assert(!fc_tle->resjunk);

				appendStringInfoString(fc_buf, fc_sep);
				fc_sep = ", ";

				appendStringInfoString(fc_buf,
									   quote_identifier(get_attname(fc_rte->relid,
																	fc_tle->resno,
																	false)));
				fc_strippedexprs = lappend(fc_strippedexprs,
										fc_processIndirection((Node *) fc_tle->expr,
														   fc_context));
			}
			if (fc_action->targetList)
				appendStringInfoChar(fc_buf, ')');

			if (fc_action->override)
			{
				if (fc_action->override == OVERRIDING_SYSTEM_VALUE)
					appendStringInfoString(fc_buf, " OVERRIDING SYSTEM VALUE");
				else if (fc_action->override == OVERRIDING_USER_VALUE)
					appendStringInfoString(fc_buf, " OVERRIDING USER VALUE");
			}

			if (fc_strippedexprs)
			{
				fc_appendContextKeyword(fc_context, " VALUES (",
									 -PRETTYINDENT_STD, PRETTYINDENT_STD, 4);
				get_rule_list_toplevel(fc_strippedexprs, fc_context, false);
				appendStringInfoChar(fc_buf, ')');
			}
			else
				appendStringInfoString(fc_buf, " DEFAULT VALUES");
		}
		else if (fc_action->commandType == CMD_UPDATE)
		{
			appendStringInfoString(fc_buf, "UPDATE SET ");
			get_update_query_targetlist_def(fc_query, fc_action->targetList,
											fc_context, fc_rte);
		}
		else if (fc_action->commandType == CMD_DELETE)
			appendStringInfoString(fc_buf, "DELETE");
		else if (fc_action->commandType == CMD_NOTHING)
			appendStringInfoString(fc_buf, "DO NOTHING");
	}

	/* MERGE 目前还不支持 RETURNING */
	Assert(fc_query->returningList == NIL);
}


/* ----------
 * get_utility_query_def			- 解析 UTILITY 解析树
 * ----------
 */
static void get_utility_query_def(Query *fc_query, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;

	if (fc_query->utilityStmt && IsA(fc_query->utilityStmt, NotifyStmt))
	{
		NotifyStmt *fc_stmt = (NotifyStmt *) fc_query->utilityStmt;

		fc_appendContextKeyword(fc_context, "",
							 0, PRETTYINDENT_STD, 1);
		appendStringInfo(fc_buf, "NOTIFY %s",
						 quote_identifier(fc_stmt->conditionname));
		if (fc_stmt->payload)
		{
			appendStringInfoString(fc_buf, ", ");
			fc_simple_quote_literal(fc_buf, fc_stmt->payload);
		}
	}
	else
	{
		/* 目前只有 NOTIFY 实用程序命令可以出现在规则中 */
		elog(ERROR, "unexpected utility statement type");
	}
}

/*
 * 适当地显示一个 Var。
 *
 * 在某些情况下（目前仅在递归到一个无名连接时）
 * Var 的 varlevelsup 必须相对于当前上下文进行解释；
 * levelsup 表示偏移量。
 *
 * 如果 istoplevel 为真，则 Var 位于 SELECT 的
 * 目标列表的顶层，这意味着我们需要特别处理整行 Var。
 * 我们将打印 "tab.*::typename"，这是一种肮脏的黑客方式，
 * 以防止 "tab.*" 被扩展为多个列。
 * （解析器会剥离无用的强制转换，因此在
 * 转储和重新加载中不会增加效率损失。）在这种情况下，
 * 我们过去只打印 "tab"，但这会造成歧义，
 * 如果 "tab" 也是查询中的普通列名，则会得到错误的结果。
 *
 * 返回 Var 的 attname，或者如果 Var 没有 attname（因为
 * 它是整行 Var 或者子计划输出引用），则返回 NULL。
 */
static char * get_variable(Var *fc_var, int fc_levelsup, bool fc_istoplevel, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	RangeTblEntry *fc_rte;
	AttrNumber	fc_attnum;
	int			fc_netlevelsup;
	deparse_namespace *fc_dpns;
	int			fc_varno;
	AttrNumber	fc_varattno;
	deparse_columns *fc_colinfo;
	char	   *fc_refname;
	char	   *fc_attname;

	/* 找到适当的嵌套深度 */
	fc_netlevelsup = fc_var->varlevelsup + fc_levelsup;
	if (fc_netlevelsup >= list_length(fc_context->namespaces))
		elog(ERROR, "bogus varlevelsup: %d offset %d",
			 fc_var->varlevelsup, fc_levelsup);
	fc_dpns = (deparse_namespace *) list_nth(fc_context->namespaces,
										  fc_netlevelsup);

	/*
	 * 如果我们有 Var 的句法引用，并且我们是在
	 * 解析树中工作，优先使用句法引用。否则，退回
	 * 到语义引用。 （在打印计划树时强制使用语义引用，
	 * 可能更多地是出于向后兼容性的设计选择，而不是其他原因。
	 * 但它确实使计划更明确。）
	 */
	if (fc_var->varnosyn > 0 && fc_dpns->plan == NULL)
	{
		fc_varno = fc_var->varnosyn;
		fc_varattno = fc_var->varattnosyn;
	}
	else
	{
		fc_varno = fc_var->varno;
		fc_varattno = fc_var->varattno;
	}

	/*
	 * 尝试在这个 rtable 中找到相关的 RTE。在一个计划树中，
	 * varno 可能是 OUTER_VAR 或 INNER_VAR，此时我们必须深入
	 * 子计划，或者 INDEX_VAR，处理方式类似。还要
	 * 找到之前分配给这个 RTE 的别名。
	 */
	if (fc_varno >= 1 && fc_varno <= list_length(fc_dpns->rtable))
	{
		/*
		 * 我们可能被要求将子 Var 映射到某个父关系。
		 */
		if (fc_context->appendparents && fc_dpns->appendrels)
		{
			int			fc_pvarno = fc_varno;
			AttrNumber	fc_pvarattno = fc_varattno;
			AppendRelInfo *fc_appinfo = fc_dpns->appendrels[fc_pvarno];
			bool		fc_found = false;

			/* 仅映射到继承父项，而不是 UNION ALL 添加关系 */
			while (fc_appinfo &&
				   rt_fetch(fc_appinfo->parent_relid,
							fc_dpns->rtable)->rtekind == RTE_RELATION)
			{
				fc_found = false;
				if (fc_pvarattno > 0)	/* 系统列保持不变 */
				{
					if (fc_pvarattno > fc_appinfo->num_child_cols)
						break;	/* 安全检查 */
					fc_pvarattno = fc_appinfo->parent_colnos[fc_pvarattno - 1];
					if (fc_pvarattno == 0)
						break;	/* Var 是子级的局部 */
				}

				fc_pvarno = fc_appinfo->parent_relid;
				fc_found = true;

				/* 如果父项本身也是子项，继续向上。 */
				Assert(fc_pvarno > 0 && fc_pvarno <= list_length(fc_dpns->rtable));
				fc_appinfo = fc_dpns->appendrels[fc_pvarno];
			}

			/*
			 * 如果我们找到了一个祖先 rel，并且该 rel 包含在
			 * appendparents 中，则打印该列而不是原始列。
			 */
			if (fc_found && bms_is_member(fc_pvarno, fc_context->appendparents))
			{
				fc_varno = fc_pvarno;
				fc_varattno = fc_pvarattno;
			}
		}

		fc_rte = rt_fetch(fc_varno, fc_dpns->rtable);
		fc_refname = (char *) list_nth(fc_dpns->rtable_names, fc_varno - 1);
		fc_colinfo = deparse_columns_fetch(fc_varno, fc_dpns);
		fc_attnum = fc_varattno;
	}
	else
	{
		fc_resolve_special_varno((Node *) fc_var, fc_context,
							  get_special_variable, NULL);
		return NULL;
	}

	/*
	 * 规划器有时会发出引用子查询目标列表中 resjunk 元素的 Vars
	 * （这目前仅在它选择为 SubqueryScan 或 CteScan 节点生成“物理 tlist”
	 * 时才有可能）。虽然我们更愿意使用子查询的别名打印引用子查询的 Vars，
	 * 但对于 resjunk 项目这是不可能的，因为它们没有别名。因此，在这种情况下，
	 * 向下钻取子计划并打印引用的 tlist 项目的内容。这是可行的，因为在计划树中，
	 * 这种 Vars 只能出现在 SubqueryScan 或 CteScan 节点中，并且我们会将 
	 * dpns->inner_plan 设置为引用子计划节点。
	 */
	if ((fc_rte->rtekind == RTE_SUBQUERY || fc_rte->rtekind == RTE_CTE) &&
		fc_attnum > list_length(fc_rte->eref->colnames) &&
		fc_dpns->inner_plan)
	{
		TargetEntry *fc_tle;
		deparse_namespace fc_save_dpns;

		fc_tle = get_tle_by_resno(fc_dpns->inner_tlist, fc_attnum);
		if (!fc_tle)
			elog(ERROR, "invalid attnum %d for relation \"%s\"",
				 fc_attnum, fc_rte->eref->aliasname);

		Assert(fc_netlevelsup == 0);
		fc_push_child_plan(fc_dpns, fc_dpns->inner_plan, &fc_save_dpns);

		/*
		 * 强制括号，因为我们的调用者可能假设 Var 是
		 * 简单的表达式。
		 */
		if (!IsA(fc_tle->expr, Var))
			appendStringInfoChar(fc_buf, '(');
		get_rule_expr((Node *) fc_tle->expr, fc_context, true);
		if (!IsA(fc_tle->expr, Var))
			appendStringInfoChar(fc_buf, ')');

		fc_pop_child_plan(fc_dpns, &fc_save_dpns);
		return NULL;
	}

	/*
	 * 如果是一个无名连接，则查看别名变量的扩展。
	 * 如果它是对输入变量的简单引用，则递归
	 * 打印该变量的名称。如果不是简单引用，
	 * 我们只能打印未限定的连接列名称。 （这只能发生在JOIN USING中的
	 * “危险”合并列；我们之前很小心地确保在这种情况下未限定的列名是唯一的。）
	 *
	 * 这在反编译计划树时是行不通的，因为我们在规划后不存储
	 * joinaliasvars 列表；但是计划树中不应该包含连接别名变量。
	 */
	if (fc_rte->rtekind == RTE_JOIN && fc_rte->alias == NULL)
	{
		if (fc_rte->joinaliasvars == NIL)
			elog(ERROR, "cannot decompile join alias var in plan tree");
		if (fc_attnum > 0)
		{
			Var		   *fc_aliasvar;

			fc_aliasvar = (Var *) list_nth(fc_rte->joinaliasvars, fc_attnum - 1);
			/* 我们故意不在这里去除隐式强制类型转换 */
			if (fc_aliasvar && IsA(fc_aliasvar, Var))
			{
				return get_variable(fc_aliasvar, fc_var->varlevelsup + fc_levelsup,
									fc_istoplevel, fc_context);
			}
		}

		/*
		 * 无名连接没有 refname。 （注意：由于它是无名的，
		 * 用户没有办法引用它来创建一个整体行 Var。
		 * 所以我们在下面不必处理这种情况。）
		 */
		Assert(fc_refname == NULL);
	}

	if (fc_attnum == InvalidAttrNumber)
		fc_attname = NULL;
	else if (fc_attnum > 0)
	{
		/* 从 colinfo 结构获取要使用的列名 */
		if (fc_attnum > fc_colinfo->num_cols)
			elog(ERROR, "invalid attnum %d for relation \"%s\"",
				 fc_attnum, fc_rte->eref->aliasname);
		fc_attname = fc_colinfo->colnames[fc_attnum - 1];

		/*
		 * 如果我们找到一个引用已删除列的 Var，似乎打印某些内容（任何内容）
		 * 比失败更好。一般来说，这不应发生，但以前在某些涉及
		 * 返回命名复合类型的函数的情况下是可能的，也许现在仍然存在一些错误。
		 */
		if (fc_attname == NULL)
			fc_attname = "?dropped?column?";
	}
	else
	{
		/* 系统列 - 名称是固定的，从目录中获取 */
		fc_attname = get_rte_attribute_name(fc_rte, fc_attnum);
	}

	if (fc_refname && (fc_context->varprefix || fc_attname == NULL))
	{
		appendStringInfoString(fc_buf, quote_identifier(fc_refname));
		appendStringInfoChar(fc_buf, '.');
	}
	if (fc_attname)
		appendStringInfoString(fc_buf, quote_identifier(fc_attname));
	else
	{
		appendStringInfoChar(fc_buf, '*');
		if (fc_istoplevel)
			appendStringInfo(fc_buf, "::%s",
							 format_type_with_typemod(fc_var->vartype,
													  fc_var->vartypmod));
	}

	return fc_attname;
}

/*
 * 反解析一个引用 OUTER_VAR、INNER_VAR 或 INDEX_VAR 的 Var。
 * 此例程实际上是 resolve_special_varno 的回调，用于处理
 * 查找正确的 TargetEntry。我们获取该 TargetEntry 中包含的表达式
 * 并只需反解析它，这项工作可以交给 get_rule_expr。
 */
static void get_special_variable(Node *fc_node, deparse_context *fc_context, void *fc_callback_arg)
{
	StringInfo	fc_buf = fc_context->buf;

	/*
	 * 对于非 Var 引用，强制括号，因为我们的调用者可能
	 * 假设 Var 是简单的表达式。
	 */
	if (!IsA(fc_node, Var))
		appendStringInfoChar(fc_buf, '(');
	get_rule_expr(fc_node, fc_context, true);
	if (!IsA(fc_node, Var))
		appendStringInfoChar(fc_buf, ')');
}

/*
 * 在计划引用中追踪特殊 varnos（OUTER_VAR、INNER_VAR、
 * INDEX_VAR），直到找到真实的 Var 或某种非 Var 节点；然后，
 * 调用提供的回调。
 */
static void fc_resolve_special_varno(Node *fc_node, deparse_context *fc_context,
					  rsv_callback fc_callback, void *fc_callback_arg)
{
	Var		   *fc_var;
	deparse_namespace *fc_dpns;

	/* 此函数是递归的，所以让我们小心谨慎。 */
	check_stack_depth();

	/* 如果不是 Var，调用回调。 */
	if (!IsA(fc_node, Var))
	{
		(*fc_callback) (fc_node, fc_context, fc_callback_arg);
		return;
	}

	/* 找到适当的嵌套深度 */
	fc_var = (Var *) fc_node;
	fc_dpns = (deparse_namespace *) list_nth(fc_context->namespaces,
										  fc_var->varlevelsup);

	/*
	 * 如果 varno 是特殊的，则递归。 （不用担心 varnosyn；
	 * 如果我们在这里，我们已经决定不使用它。）
	 */
	if (fc_var->varno == OUTER_VAR && fc_dpns->outer_tlist)
	{
		TargetEntry *fc_tle;
		deparse_namespace fc_save_dpns;
		Bitmapset  *fc_save_appendparents;

		fc_tle = get_tle_by_resno(fc_dpns->outer_tlist, fc_var->varattno);
		if (!fc_tle)
			elog(ERROR, "bogus varattno for OUTER_VAR var: %d", fc_var->varattno);

		/*
		 * 如果我们下降到 Append 或 MergeAppend 的第一个子节点，
		 * 更新 appendparents。这将影响解解析最终解析的
		 * 子表达式中出现的所有 Vars。
		 */
		fc_save_appendparents = fc_context->appendparents;

		if (IsA(fc_dpns->plan, Append))
			fc_context->appendparents = bms_union(fc_context->appendparents,
											   ((Append *) fc_dpns->plan)->apprelids);
		else if (IsA(fc_dpns->plan, MergeAppend))
			fc_context->appendparents = bms_union(fc_context->appendparents,
											   ((MergeAppend *) fc_dpns->plan)->apprelids);

		fc_push_child_plan(fc_dpns, fc_dpns->outer_plan, &fc_save_dpns);
		fc_resolve_special_varno((Node *) fc_tle->expr, fc_context,
							  fc_callback, fc_callback_arg);
		fc_pop_child_plan(fc_dpns, &fc_save_dpns);
		fc_context->appendparents = fc_save_appendparents;
		return;
	}
	else if (fc_var->varno == INNER_VAR && fc_dpns->inner_tlist)
	{
		TargetEntry *fc_tle;
		deparse_namespace fc_save_dpns;

		fc_tle = get_tle_by_resno(fc_dpns->inner_tlist, fc_var->varattno);
		if (!fc_tle)
			elog(ERROR, "bogus varattno for INNER_VAR var: %d", fc_var->varattno);

		fc_push_child_plan(fc_dpns, fc_dpns->inner_plan, &fc_save_dpns);
		fc_resolve_special_varno((Node *) fc_tle->expr, fc_context,
							  fc_callback, fc_callback_arg);
		fc_pop_child_plan(fc_dpns, &fc_save_dpns);
		return;
	}
	else if (fc_var->varno == INDEX_VAR && fc_dpns->index_tlist)
	{
		TargetEntry *fc_tle;

		fc_tle = get_tle_by_resno(fc_dpns->index_tlist, fc_var->varattno);
		if (!fc_tle)
			elog(ERROR, "bogus varattno for INDEX_VAR var: %d", fc_var->varattno);

		fc_resolve_special_varno((Node *) fc_tle->expr, fc_context,
							  fc_callback, fc_callback_arg);
		return;
	}
	else if (fc_var->varno < 1 || fc_var->varno > list_length(fc_dpns->rtable))
		elog(ERROR, "bogus varno: %d", fc_var->varno);

	/* 不是特殊的。直接调用回调。 */
	(*fc_callback) (fc_node, fc_context, fc_callback_arg);
}

/*
 * 获取复合类型表达式的字段名称。表达式通常是一个 Var，
 * 但我们也处理其他情况。
 *
 * levelsup 是一个额外的偏移量，用于正确解释 Var 的 varlevelsup。
 *
 * 当表达式具有命名复合类型时，这是相对直接的；
 * 我们只需在目录中查找类型。然而，类型也可能是 RECORD。
 * 由于实际的表或视图列不允许具有 RECORD 类型，
 * 类型为 RECORD 的 Var 必须引用一个 JOIN 或 FUNCTION RTE
 * 或子查询输出。我们向下钻取以找到最终的定义表达式，
 * 并尝试从中推断字段名称。如果我们无法确定名称，会报错。
 *
 * 类似地，类型为 RECORD 的 PARAM 必须引用某个
 * 可确定复合类型的表达式。
 */
static const char * get_name_for_var_field(Var *fc_var, int fc_fieldno,
					   int fc_levelsup, deparse_context *fc_context)
{
	RangeTblEntry *fc_rte;
	AttrNumber	fc_attnum;
	int			fc_netlevelsup;
	deparse_namespace *fc_dpns;
	int			fc_varno;
	AttrNumber	fc_varattno;
	TupleDesc	fc_tupleDesc;
	Node	   *fc_expr;

	/*
	 * 如果它是从整体行 Var 扩展的 RowExpr，使用附加的列名。
	 * （我们可以让 get_expr_result_tupdesc() 处理此事，
	 * 但直接提取所需的名称要便宜得多。）
	 */
	if (IsA(fc_var, RowExpr))
	{
		RowExpr    *fc_r = (RowExpr *) fc_var;

		if (fc_fieldno > 0 && fc_fieldno <= list_length(fc_r->colnames))
			return strVal(list_nth(fc_r->colnames, fc_fieldno - 1));
	}

	/*
	 * 如果它是类型为 RECORD 的 Param，尝试找出 Param 引用的内容。
	 */
	if (IsA(fc_var, Param))
	{
		Param	   *fc_param = (Param *) fc_var;
		ListCell   *fc_ancestor_cell;

		fc_expr = fc_find_param_referent(fc_param, fc_context, &fc_dpns, &fc_ancestor_cell);
		if (fc_expr)
		{
			/* 找到匹配项，递归以解码字段名称 */
			deparse_namespace fc_save_dpns;
			const char *fc_result;

			fc_push_ancestor_plan(fc_dpns, fc_ancestor_cell, &fc_save_dpns);
			fc_result = get_name_for_var_field((Var *) fc_expr, fc_fieldno,
											0, fc_context);
			fc_pop_ancestor_plan(fc_dpns, &fc_save_dpns);
			return fc_result;
		}
	}

	/*
	 * 如果它是类型为 RECORD 的 Var，我们必须找出 Var 引用的内容；
	 * 如果不是，我们可以使用 get_expr_result_tupdesc()。
	 */
	if (!IsA(fc_var, Var) ||
		fc_var->vartype != RECORDOID)
	{
		fc_tupleDesc = get_expr_result_tupdesc((Node *) fc_var, false);
		/* 获取 tupdesc，因此我们可以提取字段名称 */
		Assert(fc_fieldno >= 1 && fc_fieldno <= fc_tupleDesc->natts);
		return NameStr(TupleDescAttr(fc_tupleDesc, fc_fieldno - 1)->attname);
	}

	/* 找到适当的嵌套深度 */
	fc_netlevelsup = fc_var->varlevelsup + fc_levelsup;
	if (fc_netlevelsup >= list_length(fc_context->namespaces))
		elog(ERROR, "bogus varlevelsup: %d offset %d",
			 fc_var->varlevelsup, fc_levelsup);
	fc_dpns = (deparse_namespace *) list_nth(fc_context->namespaces,
										  fc_netlevelsup);

	
/*
	 * 如果我们有变量的语法引用，并且我们正在处理一个
	 * 解析树，优先使用语法引用。否则，退回到
	 * 语义引用。（请参见get_variable()中的注释。）
	 */
	if (fc_var->varnosyn > 0 && fc_dpns->plan == NULL)
	{
		fc_varno = fc_var->varnosyn;
		fc_varattno = fc_var->varattnosyn;
	}
	else
	{
		fc_varno = fc_var->varno;
		fc_varattno = fc_var->varattno;
	}

	/*
	 * 尝试在这个rtable中找到相关的RTE。在计划树中，
	 * varno很可能是OUTER_VAR或INNER_VAR，在这种情况下
	 * 我们必须深入到子计划中，或者是INDEX_VAR，
	 * 它的解析方式类似。
	 *
	 * 注意：与get_variable和resolve_special_varno不同，
	 * 我们不需要担心继承映射：子Var应该具有与
	 * 其父级相同的数据类型，而在这里我们实际上只对
	 * Var的类型感兴趣。
	 */
	if (fc_varno >= 1 && fc_varno <= list_length(fc_dpns->rtable))
	{
		fc_rte = rt_fetch(fc_varno, fc_dpns->rtable);
		fc_attnum = fc_varattno;
	}
	else if (fc_varno == OUTER_VAR && fc_dpns->outer_tlist)
	{
		TargetEntry *fc_tle;
		deparse_namespace fc_save_dpns;
		const char *fc_result;

		fc_tle = get_tle_by_resno(fc_dpns->outer_tlist, fc_varattno);
		if (!fc_tle)
			elog(ERROR, "bogus varattno for OUTER_VAR var: %d", fc_varattno);

		Assert(fc_netlevelsup == 0);
		fc_push_child_plan(fc_dpns, fc_dpns->outer_plan, &fc_save_dpns);

		fc_result = get_name_for_var_field((Var *) fc_tle->expr, fc_fieldno,
										fc_levelsup, fc_context);

		fc_pop_child_plan(fc_dpns, &fc_save_dpns);
		return fc_result;
	}
	else if (fc_varno == INNER_VAR && fc_dpns->inner_tlist)
	{
		TargetEntry *fc_tle;
		deparse_namespace fc_save_dpns;
		const char *fc_result;

		fc_tle = get_tle_by_resno(fc_dpns->inner_tlist, fc_varattno);
		if (!fc_tle)
			elog(ERROR, "bogus varattno for INNER_VAR var: %d", fc_varattno);

		Assert(fc_netlevelsup == 0);
		fc_push_child_plan(fc_dpns, fc_dpns->inner_plan, &fc_save_dpns);

		fc_result = get_name_for_var_field((Var *) fc_tle->expr, fc_fieldno,
										fc_levelsup, fc_context);

		fc_pop_child_plan(fc_dpns, &fc_save_dpns);
		return fc_result;
	}
	else if (fc_varno == INDEX_VAR && fc_dpns->index_tlist)
	{
		TargetEntry *fc_tle;
		const char *fc_result;

		fc_tle = get_tle_by_resno(fc_dpns->index_tlist, fc_varattno);
		if (!fc_tle)
			elog(ERROR, "bogus varattno for INDEX_VAR var: %d", fc_varattno);

		Assert(fc_netlevelsup == 0);

		fc_result = get_name_for_var_field((Var *) fc_tle->expr, fc_fieldno,
										fc_levelsup, fc_context);

		return fc_result;
	}
	else
	{
		elog(ERROR, "bogus varno: %d", fc_varno);
		return NULL;			/* 保持编译器安静 */
	}

	if (fc_attnum == InvalidAttrNumber)
	{
		/* Var是对RTE的整行引用，因此选择正确的字段 */
		return get_rte_attribute_name(fc_rte, fc_fieldno);
	}

	/*
	 * 这部分的逻辑基本上与解析器的
	 * expandRecordVariable()函数相同，但我们处理的是输入
	 * 上下文的不同表示，并且我们只需要一个字段名称
	 * 而不是TupleDesc。此外，当对计划树进行反解析时，
	 * 我们需要特殊情况来查找子查询和CTE子计划。
	 */
	fc_expr = (Node *) fc_var;		/* 如果无法深入，则默认值 */

	switch (fc_rte->rtekind)
	{
		case RTE_RELATION:
		case RTE_VALUES:
		case RTE_NAMEDTUPLESTORE:
		case RTE_RESULT:

			/*
			 * 这种情况不应该发生：表、值列表或ENR的列不应具有RECORD类型。穿越并在底部失败（最有可能）。
			 */
			break;
		case RTE_SUBQUERY:
			/* 子查询中的 FROM：检查子选择的输出表达式 */
			{
				if (fc_rte->subquery)
				{
					TargetEntry *fc_ste = get_tle_by_resno(fc_rte->subquery->targetList,
														fc_attnum);

					if (fc_ste == NULL || fc_ste->resjunk)
						elog(ERROR, "subquery %s does not have attribute %d",
							 fc_rte->eref->aliasname, fc_attnum);
					fc_expr = (Node *) fc_ste->expr;
					if (IsA(fc_expr, Var))
					{
						/*
						 * 递归进入子选择以查看它的Var
						 * 引用到什么。我们必须构建一个额外级别的
						 * 名称空间，以跟上子选择中的varlevelsup；
						 * 此外，子查询RTE可能来自外部查询级别，
						 * 在这种情况下，子选择的名称空间必须将
						 * 该外部级别作为父名称空间。
						 */
						List	   *fc_save_nslist = fc_context->namespaces;
						List	   *fc_parent_namespaces;
						deparse_namespace fc_mydpns;
						const char *fc_result;

						fc_parent_namespaces = list_copy_tail(fc_context->namespaces,
														   fc_netlevelsup);

						set_deparse_for_query(&fc_mydpns, fc_rte->subquery,
											  fc_parent_namespaces);

						fc_context->namespaces = lcons(&fc_mydpns, fc_parent_namespaces);

						fc_result = get_name_for_var_field((Var *) fc_expr, fc_fieldno,
														0, fc_context);

						fc_context->namespaces = fc_save_nslist;

						return fc_result;
					}
					/* 否则穿越检查表达式 */
				}
				else
				{
					/*
					 * 我们正在反解析一个计划树，因此我们没有完整的
					 * RTE条目（特别是，rte->subquery为NULL）。
					 * 但通常我们唯一能看到的直接
					 * 引用SUBQUERY RTE的Var是在SubqueryScan计划
					 * 节点中，因此我们可以查看子计划的tlist
					 * 而不是。如果子查询被证明为空并被优化掉：
					 * 那么我们会在没有子节点的Result节点中找到
					 * 这样的Var，而计划树中没有任何信息让我们
					 * 确定它最初引用了什么。在这种情况下，退回到
					 * 打印“fN”，与RowExpr的默认列名称类似。
					 */
					TargetEntry *fc_tle;
					deparse_namespace fc_save_dpns;
					const char *fc_result;

					if (!fc_dpns->inner_plan)
					{
						char	   *fc_dummy_name = palloc(32);

						Assert(fc_dpns->plan && IsA(fc_dpns->plan, Result));
						snprintf(fc_dummy_name, 32, "f%d", fc_fieldno);
						return fc_dummy_name;
					}
					Assert(fc_dpns->plan && IsA(fc_dpns->plan, SubqueryScan));

					fc_tle = get_tle_by_resno(fc_dpns->inner_tlist, fc_attnum);
					if (!fc_tle)
						elog(ERROR, "bogus varattno for subquery var: %d",
							 fc_attnum);
					Assert(fc_netlevelsup == 0);
					fc_push_child_plan(fc_dpns, fc_dpns->inner_plan, &fc_save_dpns);

					fc_result = get_name_for_var_field((Var *) fc_tle->expr, fc_fieldno,
													fc_levelsup, fc_context);

					fc_pop_child_plan(fc_dpns, &fc_save_dpns);
					return fc_result;
				}
			}
			break;
		case RTE_JOIN:
			/* 连接RTE --- 递归检查别名变量 */
			if (fc_rte->joinaliasvars == NIL)
				elog(ERROR, "cannot decompile join alias var in plan tree");
			Assert(fc_attnum > 0 && fc_attnum <= list_length(fc_rte->joinaliasvars));
			fc_expr = (Node *) list_nth(fc_rte->joinaliasvars, fc_attnum - 1);
			Assert(fc_expr != NULL);
			/* 我们故意不在这里去除隐式强制类型转换 */
			if (IsA(fc_expr, Var))
				return get_name_for_var_field((Var *) fc_expr, fc_fieldno,
											  fc_var->varlevelsup + fc_levelsup,
											  fc_context);
			/* 否则穿越检查表达式 */
			break;
		case RTE_FUNCTION:
		case RTE_TABLEFUNC:

			/*
			 * 我们无法到达这里，除非一个函数声明了其中一个结果列为RECORD，这是不允许的。
			 */
			break;
		case RTE_CTE:
			/* CTE引用：检查子查询的输出表达式 */
			{
				CommonTableExpr *fc_cte = NULL;
				Index		fc_ctelevelsup;
				ListCell   *fc_lc;

				/*
				 * 尝试使用名称空间堆栈查找引用的CTE。
				 */
				fc_ctelevelsup = fc_rte->ctelevelsup + fc_netlevelsup;
				if (fc_ctelevelsup >= list_length(fc_context->namespaces))
					fc_lc = NULL;
				else
				{
					deparse_namespace *fc_ctedpns;

					fc_ctedpns = (deparse_namespace *)
						list_nth(fc_context->namespaces, fc_ctelevelsup);
					foreach(fc_lc, fc_ctedpns->ctes)
					{
						fc_cte = (CommonTableExpr *) lfirst(fc_lc);
						if (strcmp(fc_cte->ctename, fc_rte->ctename) == 0)
							break;
					}
				}
				if (fc_lc != NULL)
				{
					Query	   *fc_ctequery = (Query *) fc_cte->ctequery;
					TargetEntry *fc_ste = get_tle_by_resno(GetCTETargetList(fc_cte),
														fc_attnum);

					if (fc_ste == NULL || fc_ste->resjunk)
						elog(ERROR, "CTE %s does not have attribute %d",
							 fc_rte->eref->aliasname, fc_attnum);
					fc_expr = (Node *) fc_ste->expr;
					if (IsA(fc_expr, Var))
					{
						/*
						 * 递归进入 CTE 以查看它的 Var 引用。 
						 * 我们必须构建一个额外的命名空间层次 
						 * 以与 CTE 中的 varlevelsup 保持一致； 
						 * 此外，它可能是一个外部 CTE（比较 
						 * 上面的 SUBQUERY 情况）。
						 */
						List	   *fc_save_nslist = fc_context->namespaces;
						List	   *fc_parent_namespaces;
						deparse_namespace fc_mydpns;
						const char *fc_result;

						fc_parent_namespaces = list_copy_tail(fc_context->namespaces,
														   fc_ctelevelsup);

						set_deparse_for_query(&fc_mydpns, fc_ctequery,
											  fc_parent_namespaces);

						fc_context->namespaces = lcons(&fc_mydpns, fc_parent_namespaces);

						fc_result = get_name_for_var_field((Var *) fc_expr, fc_fieldno,
														0, fc_context);

						fc_context->namespaces = fc_save_nslist;

						return fc_result;
					}
					/* 否则穿越检查表达式 */
				}
				else
				{
					/*
					 * 我们正在反解析一个计划树，因此我们没有 CTE 
					 * 列表。但是，我们通常会直接看到引用 CTE RTE 的 Var 
					 * 仅在 CteScan 或 WorkTableScan 计划节点中。 
					 * 对于这些情况，set_deparse_plan 
					 * 为 dpns->inner_plan 安排了发出 CTE 或 
					 * RecursiveUnion 结果的计划节点，我们可以 
					 * 查看它的 tlist。如果 CTE 被证明是空的，这 
					 * 可能会失败，那么退回到 "fN"。
					 */
					TargetEntry *fc_tle;
					deparse_namespace fc_save_dpns;
					const char *fc_result;

					if (!fc_dpns->inner_plan)
					{
						char	   *fc_dummy_name = palloc(32);

						Assert(fc_dpns->plan && IsA(fc_dpns->plan, Result));
						snprintf(fc_dummy_name, 32, "f%d", fc_fieldno);
						return fc_dummy_name;
					}
					Assert(fc_dpns->plan && (IsA(fc_dpns->plan, CteScan) ||
										  IsA(fc_dpns->plan, WorkTableScan)));

					fc_tle = get_tle_by_resno(fc_dpns->inner_tlist, fc_attnum);
					if (!fc_tle)
						elog(ERROR, "bogus varattno for subquery var: %d",
							 fc_attnum);
					Assert(fc_netlevelsup == 0);
					fc_push_child_plan(fc_dpns, fc_dpns->inner_plan, &fc_save_dpns);

					fc_result = get_name_for_var_field((Var *) fc_tle->expr, fc_fieldno,
													fc_levelsup, fc_context);

					fc_pop_child_plan(fc_dpns, &fc_save_dpns);
					return fc_result;
				}
			}
			break;
	}

	/*
	 * 我们现在有一个无法扩展的表达式，所以看看get_expr_result_tupdesc()是否能对其做些什么。
	 */
	fc_tupleDesc = get_expr_result_tupdesc(fc_expr, false);
	/* 获取 tupdesc，因此我们可以提取字段名称 */
	Assert(fc_fieldno >= 1 && fc_fieldno <= fc_tupleDesc->natts);
	return NameStr(TupleDescAttr(fc_tupleDesc, fc_fieldno - 1)->attname);
}

/*
 * 尝试查找可能引用上层 NestLoop 或 SubPlan 计划节点 
 * 提供的参数的 PARAM_EXEC 参数的引用表达式。
 *
 * 如果成功，返回表达式并相应地为调用 
 * push_ancestor_plan() 设置 *dpns_p 和 *ancestor_cell_p。 
 * 如果找不到引用，返回 NULL。
 */
static Node * fc_find_param_referent(Param *fc_param, deparse_context *fc_context,
					deparse_namespace **fc_dpns_p, ListCell **fc_ancestor_cell_p)
{
	/* 初始化输出参数以防止编译器警告 */
	*fc_dpns_p = NULL;
	*fc_ancestor_cell_p = NULL;

	/*
	 * 如果是 PARAM_EXEC 参数，查找匹配的 NestLoopParam 或 
	 * SubPlan 参数。这一定在当前表达式的计划节点的 
	 * 某个祖先中。
	 */
	if (fc_param->paramkind == PARAM_EXEC)
	{
		deparse_namespace *fc_dpns;
		Plan	   *fc_child_plan;
		bool		fc_in_same_plan_level;
		ListCell   *fc_lc;

		fc_dpns = (deparse_namespace *) linitial(fc_context->namespaces);
		fc_child_plan = fc_dpns->plan;
		fc_in_same_plan_level = true;

		foreach(fc_lc, fc_dpns->ancestors)
		{
			Node	   *fc_ancestor = (Node *) lfirst(fc_lc);
			ListCell   *fc_lc2;

			/*
			 * NestLoops 仅将参数传递给它们的内部子节点；此外， 
			 * 一旦我们爬出子计划，这根本不可能是正确的匹配。
			 */
			if (IsA(fc_ancestor, NestLoop) &&
				fc_child_plan == innerPlan(fc_ancestor) &&
				fc_in_same_plan_level)
			{
				NestLoop   *fc_nl = (NestLoop *) fc_ancestor;

				foreach(fc_lc2, fc_nl->nestParams)
				{
					NestLoopParam *fc_nlp = (NestLoopParam *) lfirst(fc_lc2);

					if (fc_nlp->paramno == fc_param->paramid)
					{
						/* 找到匹配项，因此返回它 */
						*fc_dpns_p = fc_dpns;
						*fc_ancestor_cell_p = fc_lc;
						return (Node *) fc_nlp->paramval;
					}
				}
			}

			/*
			 * 如果祖先是 SubPlan，检查它提供的参数。
			 */
			if (IsA(fc_ancestor, SubPlan))
			{
				SubPlan    *fc_subplan = (SubPlan *) fc_ancestor;
				ListCell   *fc_lc3;
				ListCell   *fc_lc4;

				forboth(fc_lc3, fc_subplan->parParam, fc_lc4, fc_subplan->args)
				{
					int			fc_paramid = lfirst_int(fc_lc3);
					Node	   *fc_arg = (Node *) lfirst(fc_lc4);

					if (fc_paramid == fc_param->paramid)
					{
						/*
						 * 找到匹配项，因此返回它。但是，由于 
						 * arg 中的 Vars 将在周围上下文中进行评估， 
						 * 我们必须指向 *不是* SubPlan 的下一个 
						 * 祖先项。
						 */
						ListCell   *fc_rest;

						for_each_cell(fc_rest, fc_dpns->ancestors,
									  lnext(fc_dpns->ancestors, fc_lc))
						{
							Node	   *fc_ancestor2 = (Node *) lfirst(fc_rest);

							if (!IsA(fc_ancestor2, SubPlan))
							{
								*fc_dpns_p = fc_dpns;
								*fc_ancestor_cell_p = fc_rest;
								return fc_arg;
							}
						}
						elog(ERROR, "SubPlan cannot be outermost ancestor");
					}
				}

				/* 我们已经从子计划中出现。 */
				fc_in_same_plan_level = false;

				/* SubPlan 不是一种计划，因此跳过其余部分 */
				continue;
			}

			/*
			 * 检查我们是否从当前祖先计划的 initplan 中出现。 
			 * Initplans 从不具有任何 parParams，因此无需 
			 * 搜索该列表，但我们需要知道是否应重置 
			 * in_same_plan_level。
			 */
			foreach(fc_lc2, ((Plan *) fc_ancestor)->initPlan)
			{
				SubPlan    *fc_subplan = lfirst_node(SubPlan, fc_lc2);

				if (fc_child_plan != (Plan *) list_nth(fc_dpns->subplans,
													fc_subplan->plan_id - 1))
					continue;

				/* 这里没有参数可用。 */
				Assert(fc_subplan->parParam == NIL);

				/* 我们已经从 initplan 中出现。 */
				fc_in_same_plan_level = false;
				break;
			}

			/* 没有运气，向上爬到下一个祖先 */
			fc_child_plan = (Plan *) fc_ancestor;
		}
	}

	/* 未找到引用 */
	return NULL;
}

/*
 * 以适当的方式显示参数。
 */
static void get_parameter(Param *fc_param, deparse_context *fc_context)
{
	Node	   *fc_expr;
	deparse_namespace *fc_dpns;
	ListCell   *fc_ancestor_cell;

	/*
	 * 如果是 PARAM_EXEC 参数，尝试定位计算该参数的 
	 * 表达式。注意，未找到引用并不是错误，因为 
	 * Param 可能是子计划输出而不是输入。
	 */
	fc_expr = fc_find_param_referent(fc_param, fc_context, &fc_dpns, &fc_ancestor_cell);
	if (fc_expr)
	{
		/* 找到匹配项，因此打印它 */
		deparse_namespace fc_save_dpns;
		bool		fc_save_varprefix;
		bool		fc_need_paren;

		/* 将注意力转向祖先计划节点 */
		fc_push_ancestor_plan(fc_dpns, fc_ancestor_cell, &fc_save_dpns);

		/*
		 * 强制 Var 的前缀化，因为它们不属于原始计划 
		 * 节点中扫描的关系。
		 */
		fc_save_varprefix = fc_context->varprefix;
		fc_context->varprefix = true;

		/*
		 * 参数的扩展通常是 Var、Aggref、GroupingFunc 或 
		 * 上层 Param，这不会需要额外的括号。
		 * 否则，插入括号以确保表达式看起来是原子的。
		 */
		fc_need_paren = !(IsA(fc_expr, Var) ||
					   IsA(fc_expr, Aggref) ||
					   IsA(fc_expr, GroupingFunc) ||
					   IsA(fc_expr, Param));
		if (fc_need_paren)
			appendStringInfoChar(fc_context->buf, '(');

		get_rule_expr(fc_expr, fc_context, false);

		if (fc_need_paren)
			appendStringInfoChar(fc_context->buf, ')');

		fc_context->varprefix = fc_save_varprefix;

		fc_pop_ancestor_plan(fc_dpns, &fc_save_dpns);

		return;
	}

	/*
	 * 如果是外部参数，查看最外层的命名空间是否提供 
	 * 函数参数名称。
	 */
	if (fc_param->paramkind == PARAM_EXTERN && fc_context->namespaces != NIL)
	{
		fc_dpns = llast(fc_context->namespaces);
		if (fc_dpns->argnames &&
			fc_param->paramid > 0 &&
			fc_param->paramid <= fc_dpns->numargs)
		{
			char	   *fc_argname = fc_dpns->argnames[fc_param->paramid - 1];

			if (fc_argname)
			{
				bool		fc_should_qualify = false;
				ListCell   *fc_lc;

				/*
				 * 如果存在其他带有范围表的反解析命名空间， 
				 * 则限定参数名称。这避免了在像 "RETURN a + b" 
				 * 这样的简单情况下进行限定，但在所有其他情况下 
				 * 使其安全。
				 */
				foreach(fc_lc, fc_context->namespaces)
				{
					deparse_namespace *fc_dpns = lfirst(fc_lc);

					if (list_length(fc_dpns->rtable_names) > 0)
					{
						fc_should_qualify = true;
						break;
					}
				}
				if (fc_should_qualify)
				{
					appendStringInfoString(fc_context->buf, quote_identifier(fc_dpns->funcname));
					appendStringInfoChar(fc_context->buf, '.');
				}

				appendStringInfoString(fc_context->buf, quote_identifier(fc_argname));
				return;
			}
		}
	}

	/*
	 * 不是 PARAM_EXEC，或者找不到引用：仅打印 $N。
	 */
	appendStringInfo(fc_context->buf, "$%d", fc_param->paramid);
}

/*
 * get_simple_binary_op_name
 *
 * isSimpleNode 的辅助函数
 * 将返回单字符二元运算符名称，如果不是，则返回 NULL
 */
static const char * get_simple_binary_op_name(OpExpr *fc_expr)
{
	List	   *fc_args = fc_expr->args;

	if (list_length(fc_args) == 2)
	{
		/* 二元运算符 */
		Node	   *fc_arg1 = (Node *) linitial(fc_args);
		Node	   *fc_arg2 = (Node *) lsecond(fc_args);
		const char *fc_op;

		fc_op = fc_generate_operator_name(fc_expr->opno, exprType(fc_arg1), exprType(fc_arg2));
		if (strlen(fc_op) == 1)
			return fc_op;
	}
	return NULL;
}


/*
 * isSimpleNode - 检查给定节点是否简单（不需要加括号）
 *
 *	true   : 在父节点类型的上下文中简单
 *	false  : 不简单
 */
static bool fc_isSimpleNode(Node *fc_node, Node *fc_parentNode, int fc_prettyFlags)
{
	if (!fc_node)
		return false;

	switch (nodeTag(fc_node))
	{
		case T_Var:
		case T_Const:
		case T_Param:
		case T_CoerceToDomainValue:
		case T_SetToDefault:
		case T_CurrentOfExpr:
			/* 单词：始终简单 */
			return true;

		case T_SubscriptingRef:
		case T_ArrayExpr:
		case T_RowExpr:
		case T_CoalesceExpr:
		case T_MinMaxExpr:
		case T_SQLValueFunction:
		case T_XmlExpr:
		case T_NextValueExpr:
		case T_NullIfExpr:
		case T_Aggref:
		case T_GroupingFunc:
		case T_WindowFunc:
		case T_FuncExpr:
			/* 类似函数：name(..) 或 name[..] */
			return true;

			/* CASE 关键字充当括号 */
		case T_CaseExpr:
			return true;

		case T_FieldSelect:

			/*
			 * 看起来很简单，因为 . 的优先级最高，除非父节点是
			 * T_FieldSelect 本身！
			 */
			return !IsA(fc_parentNode, FieldSelect);

		case T_FieldStore:

			/*
			 * 像 FieldSelect 一样处理（可能无关紧要）
			 */
			return !IsA(fc_parentNode, FieldStore);

		case T_CoerceToDomain:
			/* 可能简单，检查参数 */
			return fc_isSimpleNode((Node *) ((CoerceToDomain *) fc_node)->arg,
								fc_node, fc_prettyFlags);
		case T_RelabelType:
			return fc_isSimpleNode((Node *) ((RelabelType *) fc_node)->arg,
								fc_node, fc_prettyFlags);
		case T_CoerceViaIO:
			return fc_isSimpleNode((Node *) ((CoerceViaIO *) fc_node)->arg,
								fc_node, fc_prettyFlags);
		case T_ArrayCoerceExpr:
			return fc_isSimpleNode((Node *) ((ArrayCoerceExpr *) fc_node)->arg,
								fc_node, fc_prettyFlags);
		case T_ConvertRowtypeExpr:
			return fc_isSimpleNode((Node *) ((ConvertRowtypeExpr *) fc_node)->arg,
								fc_node, fc_prettyFlags);

		case T_OpExpr:
			{
				/* 取决于父节点类型；需要进一步检查 */
				if (fc_prettyFlags & PRETTYFLAG_PAREN && IsA(fc_parentNode, OpExpr))
				{
					const char *fc_op;
					const char *fc_parentOp;
					bool		fc_is_lopriop;
					bool		fc_is_hipriop;
					bool		fc_is_lopriparent;
					bool		fc_is_hipriparent;

					fc_op = get_simple_binary_op_name((OpExpr *) fc_node);
					if (!fc_op)
						return false;

					/* 我们只知道基本运算符 + - 和 * / % */
					fc_is_lopriop = (strchr("+-", *fc_op) != NULL);
					fc_is_hipriop = (strchr("*/%", *fc_op) != NULL);
					if (!(fc_is_lopriop || fc_is_hipriop))
						return false;

					fc_parentOp = get_simple_binary_op_name((OpExpr *) fc_parentNode);
					if (!fc_parentOp)
						return false;

					fc_is_lopriparent = (strchr("+-", *fc_parentOp) != NULL);
					fc_is_hipriparent = (strchr("*/%", *fc_parentOp) != NULL);
					if (!(fc_is_lopriparent || fc_is_hipriparent))
						return false;

					if (fc_is_hipriop && fc_is_lopriparent)
						return true;	/* op 的绑定优于父节点 */

					if (fc_is_lopriop && fc_is_hipriparent)
						return false;

					/*
					 * 运算符优先级相同 --- 只有在我们有
					 * (a - b) - c 时可以跳过括号，而不能是 a - (b - c)。
					 */
					if (fc_node == (Node *) linitial(((OpExpr *) fc_parentNode)->args))
						return true;

					return false;
				}
				/* 否则与 T_SubLink 等同样处理 */
			}
			/* FALLTHROUGH */

		case T_SubLink:
		case T_NullTest:
		case T_BooleanTest:
		case T_DistinctExpr:
			switch (nodeTag(fc_parentNode))
			{
				case T_FuncExpr:
					{
						/* 对 casts 和 COERCE_SQL_SYNTAX 的特殊处理 */
						CoercionForm fc_type = ((FuncExpr *) fc_parentNode)->funcformat;

						if (fc_type == COERCE_EXPLICIT_CAST ||
							fc_type == COERCE_IMPLICIT_CAST ||
							fc_type == COERCE_SQL_SYNTAX)
							return false;
						return true;	/* 自己的括号 */
					}
				case T_BoolExpr:	/* 较低的优先级 */
				case T_SubscriptingRef: /* 其他分隔符 */
				case T_ArrayExpr:	/* 其他分隔符 */
				case T_RowExpr: /* 其他分隔符 */
				case T_CoalesceExpr:	/* 自己的括号 */
				case T_MinMaxExpr:	/* 自己的括号 */
				case T_XmlExpr: /* 自己的括号 */
				case T_NullIfExpr:	/* 其他分隔符 */
				case T_Aggref:	/* 自己的括号 */
				case T_GroupingFunc:	/* 自己的括号 */
				case T_WindowFunc:	/* 自己的括号 */
				case T_CaseExpr:	/* 其他分隔符 */
					return true;
				default:
					return false;
			}

		case T_BoolExpr:
			switch (nodeTag(fc_parentNode))
			{
				case T_BoolExpr:
					if (fc_prettyFlags & PRETTYFLAG_PAREN)
					{
						BoolExprType fc_type;
						BoolExprType fc_parentType;

						fc_type = ((BoolExpr *) fc_node)->boolop;
						fc_parentType = ((BoolExpr *) fc_parentNode)->boolop;
						switch (fc_type)
						{
							case NOT_EXPR:
							case AND_EXPR:
								if (fc_parentType == AND_EXPR || fc_parentType == OR_EXPR)
									return true;
								break;
							case OR_EXPR:
								if (fc_parentType == OR_EXPR)
									return true;
								break;
						}
					}
					return false;
				case T_FuncExpr:
					{
						/* 对 casts 和 COERCE_SQL_SYNTAX 的特殊处理 */
						CoercionForm fc_type = ((FuncExpr *) fc_parentNode)->funcformat;

						if (fc_type == COERCE_EXPLICIT_CAST ||
							fc_type == COERCE_IMPLICIT_CAST ||
							fc_type == COERCE_SQL_SYNTAX)
							return false;
						return true;	/* 自己的括号 */
					}
				case T_SubscriptingRef: /* 其他分隔符 */
				case T_ArrayExpr:	/* 其他分隔符 */
				case T_RowExpr: /* 其他分隔符 */
				case T_CoalesceExpr:	/* 自己的括号 */
				case T_MinMaxExpr:	/* 自己的括号 */
				case T_XmlExpr: /* 自己的括号 */
				case T_NullIfExpr:	/* 其他分隔符 */
				case T_Aggref:	/* 自己的括号 */
				case T_GroupingFunc:	/* 自己的括号 */
				case T_WindowFunc:	/* 自己的括号 */
				case T_CaseExpr:	/* 其他分隔符 */
					return true;
				default:
					return false;
			}

		default:
			break;
	}
	/* 我们不知道的那些：在怀疑复杂中 */
	return false;
}


/*
 * appendContextKeyword - 将关键字附加到缓冲区
 *
 * 如果启用了 prettyPrint，进行换行，并调整缩进。
 * 否则，仅附加关键字。
 */
static void fc_appendContextKeyword(deparse_context *fc_context, const char *fc_str,
					 int fc_indentBefore, int fc_indentAfter, int fc_indentPlus)
{
	StringInfo	fc_buf = fc_context->buf;

	if (PRETTY_INDENT(fc_context))
	{
		int			fc_indentAmount;

		fc_context->indentLevel += fc_indentBefore;

		/* 删除缓冲区中当前存在的任何尾随空格... */
		fc_removeStringInfoSpaces(fc_buf);
		/* ...然后添加换行符和一些空格 */
		appendStringInfoChar(fc_buf, '\n');

		if (fc_context->indentLevel < PRETTYINDENT_LIMIT)
			fc_indentAmount = Max(fc_context->indentLevel, 0) + fc_indentPlus;
		else
		{
			/*
			 * 如果我们的缩进超过 PRETTYINDENT_LIMIT 个字符，尝试
			 * 通过减少每级的缩进来节省水平空间。为了获得最佳结果，
			 * 这里的缩放因子应该能够整除所有添加到 indentLevel 的缩进
			 * （PRETTYINDENT_STD 等）。重要的是缩进不能无限增长，
			 * 否则深层嵌套的树使用 O(N^2) 的空白；因此，我们还将其包裹在 PRETTYINDENT_LIMIT 模数内。
			 */
			fc_indentAmount = PRETTYINDENT_LIMIT +
				(fc_context->indentLevel - PRETTYINDENT_LIMIT) /
				(PRETTYINDENT_STD / 2);
			fc_indentAmount %= PRETTYINDENT_LIMIT;
			/* 缩放/包装逻辑影响 indentLevel，但不影响 indentPlus */
			fc_indentAmount += fc_indentPlus;
		}
		appendStringInfoSpaces(fc_buf, fc_indentAmount);

		appendStringInfoString(fc_buf, fc_str);

		fc_context->indentLevel += fc_indentAfter;
		if (fc_context->indentLevel < 0)
			fc_context->indentLevel = 0;
	}
	else
		appendStringInfoString(fc_buf, fc_str);
}

/*
 * removeStringInfoSpaces - 从缓冲区删除尾随空格。
 *
 * 可能在某个时候应该移动到 stringinfo.c。
 */
static void fc_removeStringInfoSpaces(StringInfo fc_str)
{
	while (fc_str->len > 0 && fc_str->data[fc_str->len - 1] == ' ')
		fc_str->data[--(fc_str->len)] = '\0';
}


/*
 * get_rule_expr_paren - 使用 get_rule_expr 反解析表达式，
 * 如有必要，用括号包围字符串以实现 prettyPrint。
 *
 * 如果 prettyFlags=0，绝不要包围，因为它在调用节点中完成。
 *
 * 任何不通过 SQL 语法（带有括号，非运算符关键字如 CASE/WHEN/ON，或者逗号等）包围其参数节点的节点
 * 应该使用 get_rule_expr_paren 而不是 get_rule_expr，以便可以添加括号。
 */
static void get_rule_expr_paren(Node *fc_node, deparse_context *fc_context,
					bool fc_showimplicit, Node *fc_parentNode)
{
	bool		fc_need_paren;

	fc_need_paren = PRETTY_PAREN(fc_context) &&
		!fc_isSimpleNode(fc_node, fc_parentNode, fc_context->prettyFlags);

	if (fc_need_paren)
		appendStringInfoChar(fc_context->buf, '(');

	get_rule_expr(fc_node, fc_context, fc_showimplicit);

	if (fc_need_paren)
		appendStringInfoChar(fc_context->buf, ')');
}


/* ----------
 * get_rule_expr - 反解析一个表达式
 *
 * 注意：showimplicit 决定是否显示位于表达式树顶部的任何隐式转换。
 * 这是一个传递的参数，而不是上下文结构的字段，因为随着我们向下递归到表达式中，我们更改该值。
 * 一般情况下，当结果类型确定时，我们会抑制隐式转换（例如，OR 的参数必须是布尔值）。 
 * 我们会为函数和运算符的参数显示隐式转换，因为这对于确保在重新解析表达式时选择相同的函数或运算符是必要的。
 * ----------
 */
static void get_rule_expr(Node *fc_node, deparse_context *fc_context,
			  bool fc_showimplicit)
{
	StringInfo	fc_buf = fc_context->buf;

	if (fc_node == NULL)
		return;

	/* 防止过长或深度嵌套的查询 */
	CHECK_FOR_INTERRUPTS();
	check_stack_depth();

	/*
	 * 每个 get_rule_expr 的级别必须发出一个不可分割的项
	 * （如有必要，括起来）以确保结果被反解析为相同的
	 * 表达式树。唯一的例外是当输入是一个 List 时，
	 * 我们以逗号分隔的方式发出组件项目，而没有任何外部
	 * 装饰；这对大多数调用者都是方便的。
	 */
	switch (nodeTag(fc_node))
	{
		case T_Var:
			(void) get_variable((Var *) fc_node, 0, false, fc_context);
			break;

		case T_Const:
			get_const_expr((Const *) fc_node, fc_context, 0);
			break;

		case T_Param:
			get_parameter((Param *) fc_node, fc_context);
			break;

		case T_Aggref:
			get_agg_expr((Aggref *) fc_node, fc_context, (Aggref *) fc_node);
			break;

		case T_GroupingFunc:
			{
				GroupingFunc *fc_gexpr = (GroupingFunc *) fc_node;

				appendStringInfoString(fc_buf, "GROUPING(");
				get_rule_expr((Node *) fc_gexpr->args, fc_context, true);
				appendStringInfoChar(fc_buf, ')');
			}
			break;

		case T_WindowFunc:
			get_windowfunc_expr((WindowFunc *) fc_node, fc_context);
			break;

		case T_SubscriptingRef:
			{
				SubscriptingRef *fc_sbsref = (SubscriptingRef *) fc_node;
				bool		fc_need_parens;

				/*
				 * 如果参数是 CaseTestExpr，我们必须在一个
				 * FieldStore 内，即我们正在为一个复合列中的数组
				 * 元素赋值。由于我们已经放弃了显示 FieldStore 的目标信息，
				 * 这里也只需放弃，只显示赋值源
				 * 表达式。
				 */
				if (IsA(fc_sbsref->refexpr, CaseTestExpr))
				{
					Assert(fc_sbsref->refassgnexpr);
					get_rule_expr((Node *) fc_sbsref->refassgnexpr,
								  fc_context, fc_showimplicit);
					break;
				}

				/*
				 * 除非是简单的 Var 或 FieldSelect，否则对参数进行括号处理。
				 * （特别是如果是另一个 SubscriptingRef，我们 *必须* 括起来以避免
				 * 混淆。）
				 */
				fc_need_parens = !IsA(fc_sbsref->refexpr, Var) &&
					!IsA(fc_sbsref->refexpr, FieldSelect);
				if (fc_need_parens)
					appendStringInfoChar(fc_buf, '(');
				get_rule_expr((Node *) fc_sbsref->refexpr, fc_context, fc_showimplicit);
				if (fc_need_parens)
					appendStringInfoChar(fc_buf, ')');

				/*
				 * 如果有 refassgnexpr，我们希望将节点打印为
				 * "container[subscripts] := refassgnexpr" 的格式。
				 * 这不是合法的 SQL，因此 INSERT 或 UPDATE
				 * 语句的反编译应该始终使用 processIndirection 作为语句级语法的一部分。
				 * 我们应该只在 EXPLAIN 尝试打印这样的语句生成的计划的 targetlist 时看到这个。
				 */
				if (fc_sbsref->refassgnexpr)
				{
					Node	   *fc_refassgnexpr;

					/*
					 * 使用 processIndirection 打印此节点的下标
					 * 以及任何附加的字段选择或
					 * 在直接后代中的下标。它返回实际被“赋值”的 RHS 表达式。
					 */
					fc_refassgnexpr = fc_processIndirection(fc_node, fc_context);
					appendStringInfoString(fc_buf, " := ");
					get_rule_expr(fc_refassgnexpr, fc_context, fc_showimplicit);
				}
				else
				{
					/* 只是一个普通的容器提取，因此打印下标 */
					fc_printSubscripts(fc_sbsref, fc_context);
				}
			}
			break;

		case T_FuncExpr:
			get_func_expr((FuncExpr *) fc_node, fc_context, fc_showimplicit);
			break;

		case T_NamedArgExpr:
			{
				NamedArgExpr *fc_na = (NamedArgExpr *) fc_node;

				appendStringInfo(fc_buf, "%s => ", quote_identifier(fc_na->name));
				get_rule_expr((Node *) fc_na->arg, fc_context, fc_showimplicit);
			}
			break;

		case T_OpExpr:
			get_oper_expr((OpExpr *) fc_node, fc_context);
			break;

		case T_DistinctExpr:
			{
				DistinctExpr *fc_expr = (DistinctExpr *) fc_node;
				List	   *fc_args = fc_expr->args;
				Node	   *fc_arg1 = (Node *) linitial(fc_args);
				Node	   *fc_arg2 = (Node *) lsecond(fc_args);

				if (!PRETTY_PAREN(fc_context))
					appendStringInfoChar(fc_buf, '(');
				get_rule_expr_paren(fc_arg1, fc_context, true, fc_node);
				appendStringInfoString(fc_buf, " IS DISTINCT FROM ");
				get_rule_expr_paren(fc_arg2, fc_context, true, fc_node);
				if (!PRETTY_PAREN(fc_context))
					appendStringInfoChar(fc_buf, ')');
			}
			break;

		case T_NullIfExpr:
			{
				NullIfExpr *fc_nullifexpr = (NullIfExpr *) fc_node;

				appendStringInfoString(fc_buf, "NULLIF(");
				get_rule_expr((Node *) fc_nullifexpr->args, fc_context, true);
				appendStringInfoChar(fc_buf, ')');
			}
			break;

		case T_ScalarArrayOpExpr:
			{
				ScalarArrayOpExpr *fc_expr = (ScalarArrayOpExpr *) fc_node;
				List	   *fc_args = fc_expr->args;
				Node	   *fc_arg1 = (Node *) linitial(fc_args);
				Node	   *fc_arg2 = (Node *) lsecond(fc_args);

				if (!PRETTY_PAREN(fc_context))
					appendStringInfoChar(fc_buf, '(');
				get_rule_expr_paren(fc_arg1, fc_context, true, fc_node);
				appendStringInfo(fc_buf, " %s %s (",
								 fc_generate_operator_name(fc_expr->opno,
														exprType(fc_arg1),
														get_base_element_type(exprType(fc_arg2))),
								 fc_expr->useOr ? "ANY" : "ALL");
				get_rule_expr_paren(fc_arg2, fc_context, true, fc_node);

				
/*
				 * "x op ANY/ALL (y)" 中存在固有的模糊性，当 y 是
				 * 一个裸的子 SELECT 时。因为我们在这里，子 SELECT 必须
				 * 被视为一个标量子 SELECT，产生一个数组值以
				 * 用于 ScalarArrayOpExpr；但语法会
				 * 优先将这样的结构解释为 ANY/ALL
				 * SubLink。为了防止以这种方式错误解析输出，插入
				 * 一个虚拟强制转换（在解析分析时将被去除，
				 * 所以在转储和重新加载时不会增加任何低效）。这
				 * 确实是用户首先写下该结构以使其被接受的最可能方式。
				 */
				if (IsA(fc_arg2, SubLink) &&
					((SubLink *) fc_arg2)->subLinkType == EXPR_SUBLINK)
					appendStringInfo(fc_buf, "::%s",
									 format_type_with_typemod(exprType(fc_arg2),
															  exprTypmod(fc_arg2)));
				appendStringInfoChar(fc_buf, ')');
				if (!PRETTY_PAREN(fc_context))
					appendStringInfoChar(fc_buf, ')');
			}
			break;

		case T_BoolExpr:
			{
				BoolExpr   *fc_expr = (BoolExpr *) fc_node;
				Node	   *fc_first_arg = linitial(fc_expr->args);
				ListCell   *fc_arg;

				switch (fc_expr->boolop)
				{
					case AND_EXPR:
						if (!PRETTY_PAREN(fc_context))
							appendStringInfoChar(fc_buf, '(');
						get_rule_expr_paren(fc_first_arg, fc_context,
											false, fc_node);
						for_each_from(fc_arg, fc_expr->args, 1)
						{
							appendStringInfoString(fc_buf, " AND ");
							get_rule_expr_paren((Node *) lfirst(fc_arg), fc_context,
												false, fc_node);
						}
						if (!PRETTY_PAREN(fc_context))
							appendStringInfoChar(fc_buf, ')');
						break;

					case OR_EXPR:
						if (!PRETTY_PAREN(fc_context))
							appendStringInfoChar(fc_buf, '(');
						get_rule_expr_paren(fc_first_arg, fc_context,
											false, fc_node);
						for_each_from(fc_arg, fc_expr->args, 1)
						{
							appendStringInfoString(fc_buf, " OR ");
							get_rule_expr_paren((Node *) lfirst(fc_arg), fc_context,
												false, fc_node);
						}
						if (!PRETTY_PAREN(fc_context))
							appendStringInfoChar(fc_buf, ')');
						break;

					case NOT_EXPR:
						if (!PRETTY_PAREN(fc_context))
							appendStringInfoChar(fc_buf, '(');
						appendStringInfoString(fc_buf, "NOT ");
						get_rule_expr_paren(fc_first_arg, fc_context,
											false, fc_node);
						if (!PRETTY_PAREN(fc_context))
							appendStringInfoChar(fc_buf, ')');
						break;

					default:
						elog(ERROR, "unrecognized boolop: %d",
							 (int) fc_expr->boolop);
				}
			}
			break;

		case T_SubLink:
			get_sublink_expr((SubLink *) fc_node, fc_context);
			break;

		case T_SubPlan:
			{
				SubPlan    *fc_subplan = (SubPlan *) fc_node;

				/*
				 * 我们在规则反解析中无法看到一个已经计划好的子计划，
				 * 只能在解释查询计划时看到。我们不尝试
				 * 重新构建原始 SQL，只是引用在 EXPLAIN 结果中
				 * 出现的子计划。
				 */
				if (fc_subplan->useHashTable)
					appendStringInfo(fc_buf, "(hashed %s)", fc_subplan->plan_name);
				else
					appendStringInfo(fc_buf, "(%s)", fc_subplan->plan_name);
			}
			break;

		case T_AlternativeSubPlan:
			{
				AlternativeSubPlan *fc_asplan = (AlternativeSubPlan *) fc_node;
				ListCell   *fc_lc;

				/*
				 * 此情况在正常使用中无法达到，因为在解析树或完成的计划树中都不会出现
				 * AlternativeSubPlan。我们保留它以防有人想使用此代码打印规划器数据结构。
				 */
				appendStringInfoString(fc_buf, "(alternatives: ");
				foreach(fc_lc, fc_asplan->subplans)
				{
					SubPlan    *fc_splan = lfirst_node(SubPlan, fc_lc);

					if (fc_splan->useHashTable)
						appendStringInfo(fc_buf, "hashed %s", fc_splan->plan_name);
					else
						appendStringInfoString(fc_buf, fc_splan->plan_name);
					if (lnext(fc_asplan->subplans, fc_lc))
						appendStringInfoString(fc_buf, " or ");
				}
				appendStringInfoChar(fc_buf, ')');
			}
			break;

		case T_FieldSelect:
			{
				FieldSelect *fc_fselect = (FieldSelect *) fc_node;
				Node	   *fc_arg = (Node *) fc_fselect->arg;
				int			fc_fno = fc_fselect->fieldnum;
				const char *fc_fieldname;
				bool		fc_need_parens;

				/*
				 * 除非参数是 SubscriptingRef 或另一个 FieldSelect，否则应对参数进行括号化。
				 * 特别注意，未对 Var 参数进行括号化是错误的；在这里，简洁性不是问题，
				 * 关键是拥有正确数量的名称。
				 */
				fc_need_parens = !IsA(fc_arg, SubscriptingRef) &&
					!IsA(fc_arg, FieldSelect);
				if (fc_need_parens)
					appendStringInfoChar(fc_buf, '(');
				get_rule_expr(fc_arg, fc_context, true);
				if (fc_need_parens)
					appendStringInfoChar(fc_buf, ')');

				/*
				 * 获取并打印字段名称。
				 */
				fc_fieldname = get_name_for_var_field((Var *) fc_arg, fc_fno,
												   0, fc_context);
				appendStringInfo(fc_buf, ".%s", quote_identifier(fc_fieldname));
			}
			break;

		case T_FieldStore:
			{
				FieldStore *fc_fstore = (FieldStore *) fc_node;
				bool		fc_need_parens;

				/*
				 * 由于没有好的方法将 FieldStore 表示为真正的 SQL，
				 * 因此插入或更新语句的反编译应始终使用 processIndirection
				 * 作为语句级语法的一部分。我们只会在 EXPLAIN 尝试打印来自
				 * 这种语句的计划的目标列表时到达这里。计划的情况甚至比普通规则要困难，
				 * 因为规划器尝试将对同一字段或子字段的多次赋值合并为一个 FieldStore；
				 * 因此，我们可以看到目标字段的列表，而不仅仅是一个，参数本身可能是
				 * FieldStores。我们不费心尝试打印目标字段名称；
				 * 如果有多个字段，我们只打印源参数，并用 ROW() 将其包围。
				 * 这并不是非常完整，但可能足以满足 EXPLAIN 的目的；
				 * 尤其是因为任何更多的内容要么会令人困惑，要么会更糟
				 * 地表示计划实际上在做什么。
				 */
				fc_need_parens = (list_length(fc_fstore->newvals) != 1);
				if (fc_need_parens)
					appendStringInfoString(fc_buf, "ROW(");
				get_rule_expr((Node *) fc_fstore->newvals, fc_context, fc_showimplicit);
				if (fc_need_parens)
					appendStringInfoChar(fc_buf, ')');
			}
			break;

		case T_RelabelType:
			{
				RelabelType *fc_relabel = (RelabelType *) fc_node;
				Node	   *fc_arg = (Node *) fc_relabel->arg;

				if (fc_relabel->relabelformat == COERCE_IMPLICIT_CAST &&
					!fc_showimplicit)
				{
					/* 不显示隐式转换 */
					get_rule_expr_paren(fc_arg, fc_context, false, fc_node);
				}
				else
				{
					get_coercion_expr(fc_arg, fc_context,
									  fc_relabel->resulttype,
									  fc_relabel->resulttypmod,
									  fc_node);
				}
			}
			break;

		case T_CoerceViaIO:
			{
				CoerceViaIO *fc_iocoerce = (CoerceViaIO *) fc_node;
				Node	   *fc_arg = (Node *) fc_iocoerce->arg;

				if (fc_iocoerce->coerceformat == COERCE_IMPLICIT_CAST &&
					!fc_showimplicit)
				{
					/* 不显示隐式转换 */
					get_rule_expr_paren(fc_arg, fc_context, false, fc_node);
				}
				else
				{
					get_coercion_expr(fc_arg, fc_context,
									  fc_iocoerce->resulttype,
									  -1,
									  fc_node);
				}
			}
			break;

		case T_ArrayCoerceExpr:
			{
				ArrayCoerceExpr *fc_acoerce = (ArrayCoerceExpr *) fc_node;
				Node	   *fc_arg = (Node *) fc_acoerce->arg;

				if (fc_acoerce->coerceformat == COERCE_IMPLICIT_CAST &&
					!fc_showimplicit)
				{
					/* 不显示隐式转换 */
					get_rule_expr_paren(fc_arg, fc_context, false, fc_node);
				}
				else
				{
					get_coercion_expr(fc_arg, fc_context,
									  fc_acoerce->resulttype,
									  fc_acoerce->resulttypmod,
									  fc_node);
				}
			}
			break;

		case T_ConvertRowtypeExpr:
			{
				ConvertRowtypeExpr *fc_convert = (ConvertRowtypeExpr *) fc_node;
				Node	   *fc_arg = (Node *) fc_convert->arg;

				if (fc_convert->convertformat == COERCE_IMPLICIT_CAST &&
					!fc_showimplicit)
				{
					/* 不显示隐式转换 */
					get_rule_expr_paren(fc_arg, fc_context, false, fc_node);
				}
				else
				{
					get_coercion_expr(fc_arg, fc_context,
									  fc_convert->resulttype, -1,
									  fc_node);
				}
			}
			break;

		case T_CollateExpr:
			{
				CollateExpr *fc_collate = (CollateExpr *) fc_node;
				Node	   *fc_arg = (Node *) fc_collate->arg;

				if (!PRETTY_PAREN(fc_context))
					appendStringInfoChar(fc_buf, '(');
				get_rule_expr_paren(fc_arg, fc_context, fc_showimplicit, fc_node);
				appendStringInfo(fc_buf, " COLLATE %s",
								 generate_collation_name(fc_collate->collOid));
				if (!PRETTY_PAREN(fc_context))
					appendStringInfoChar(fc_buf, ')');
			}
			break;

		case T_CaseExpr:
			{
				CaseExpr   *fc_caseexpr = (CaseExpr *) fc_node;
				ListCell   *fc_temp;

				fc_appendContextKeyword(fc_context, "CASE",
									 0, PRETTYINDENT_VAR, 0);
				if (fc_caseexpr->arg)
				{
					appendStringInfoChar(fc_buf, ' ');
					get_rule_expr((Node *) fc_caseexpr->arg, fc_context, true);
				}
				foreach(fc_temp, fc_caseexpr->args)
				{
					CaseWhen   *fc_when = (CaseWhen *) lfirst(fc_temp);
					Node	   *fc_w = (Node *) fc_when->expr;

					if (fc_caseexpr->arg)
					{
						/*
						 * 解析器应生成形式为 "CaseTestExpr = RHS" 的 WHEN 子句，
						 * 可能在 CaseTestExpr 之上插入了隐式强制转换。
						 * 为了准确反编译规则，至关重要的是我们只显示 RHS。
						 * 然而在经过优化的表达式中，WHEN 子句几乎可以是任何东西
						 * （因为等式运算符可能已扩展为内联函数）。
						 * 如果我们不识别 WHEN 子句的形式，只需放弃并按原样显示它。
						 */
						if (IsA(fc_w, OpExpr))
						{
							List	   *fc_args = ((OpExpr *) fc_w)->args;

							if (list_length(fc_args) == 2 &&
								IsA(strip_implicit_coercions(linitial(fc_args)),
									CaseTestExpr))
								fc_w = (Node *) lsecond(fc_args);
						}
					}

					if (!PRETTY_INDENT(fc_context))
						appendStringInfoChar(fc_buf, ' ');
					fc_appendContextKeyword(fc_context, "WHEN ",
										 0, 0, 0);
					get_rule_expr(fc_w, fc_context, false);
					appendStringInfoString(fc_buf, " THEN ");
					get_rule_expr((Node *) fc_when->result, fc_context, true);
				}
				if (!PRETTY_INDENT(fc_context))
					appendStringInfoChar(fc_buf, ' ');
				fc_appendContextKeyword(fc_context, "ELSE ",
									 0, 0, 0);
				get_rule_expr((Node *) fc_caseexpr->defresult, fc_context, true);
				if (!PRETTY_INDENT(fc_context))
					appendStringInfoChar(fc_buf, ' ');
				fc_appendContextKeyword(fc_context, "END",
									 -PRETTYINDENT_VAR, 0, 0);
			}
			break;

		case T_CaseTestExpr:
			{
				/*
				 * 通常我们不应到达这里，因为对于可能包含此节点类型的表达式，
				 * 我们尝试避免递归到它。但在优化后的表达式中，我们可能无法避免这一点
				 * （请参阅 CaseExpr 的评论）。如果我们确实看到一个，就将其打印为
				 * CASE_TEST_EXPR。
				 */
				appendStringInfoString(fc_buf, "CASE_TEST_EXPR");
			}
			break;

		case T_ArrayExpr:
			{
				ArrayExpr  *fc_arrayexpr = (ArrayExpr *) fc_node;

				appendStringInfoString(fc_buf, "ARRAY[");
				get_rule_expr((Node *) fc_arrayexpr->elements, fc_context, true);
				appendStringInfoChar(fc_buf, ']');

				/*
				 * 如果数组不为空，我们假设其元素被强制转换为所需类型。
				 * 但如果为空，我们需要明确强制转换为数组类型。
				 */
				if (fc_arrayexpr->elements == NIL)
					appendStringInfo(fc_buf, "::%s",
									 format_type_with_typemod(fc_arrayexpr->array_typeid, -1));
			}
			break;

		case T_RowExpr:
			{
				RowExpr    *fc_rowexpr = (RowExpr *) fc_node;
				TupleDesc	fc_tupdesc = NULL;
				ListCell   *fc_arg;
				int			fc_i;
				char	   *fc_sep;

				/*
				 * 如果它是命名类型而不是 RECORD，我们可能需要跳过已丢弃的列
				 * 和/或声明新增列有 NULL 值。
				 */
				if (fc_rowexpr->row_typeid != RECORDOID)
				{
					fc_tupdesc = lookup_rowtype_tupdesc(fc_rowexpr->row_typeid, -1);
					Assert(list_length(fc_rowexpr->args) <= fc_tupdesc->natts);
				}

				/*
				 * SQL99 允许在有多个列时省略 "ROW"，但为了简洁起见，我们始终打印它。
				 */
				appendStringInfoString(fc_buf, "ROW(");
				fc_sep = "";
				fc_i = 0;
				foreach(fc_arg, fc_rowexpr->args)
				{
					Node	   *fc_e = (Node *) lfirst(fc_arg);

					if (fc_tupdesc == NULL ||
						!TupleDescAttr(fc_tupdesc, fc_i)->attisdropped)
					{
						appendStringInfoString(fc_buf, fc_sep);
						/* 整行 Vars 在这里需要特别处理 */
						get_rule_expr_toplevel(fc_e, fc_context, true);
						fc_sep = ", ";
					}
					fc_i++;
				}
				if (fc_tupdesc != NULL)
				{
					while (fc_i < fc_tupdesc->natts)
					{
						if (!TupleDescAttr(fc_tupdesc, fc_i)->attisdropped)
						{
							appendStringInfoString(fc_buf, fc_sep);
							appendStringInfoString(fc_buf, "NULL");
							fc_sep = ", ";
						}
						fc_i++;
					}

					ReleaseTupleDesc(fc_tupdesc);
				}
				appendStringInfoChar(fc_buf, ')');
				if (fc_rowexpr->row_format == COERCE_EXPLICIT_CAST)
					appendStringInfo(fc_buf, "::%s",
									 format_type_with_typemod(fc_rowexpr->row_typeid, -1));
			}
			break;

		case T_RowCompareExpr:
			{
				RowCompareExpr *fc_rcexpr = (RowCompareExpr *) fc_node;

				/*
				 * SQL99 允许在有多个列时省略 "ROW"，但为了简洁起见，我们始终打印它。
				 * 在 ROW 表达式内，整行 Vars 需要特别处理，因此使用 get_rule_list_toplevel。
				 */
				appendStringInfoString(fc_buf, "(ROW(");
				get_rule_list_toplevel(fc_rcexpr->largs, fc_context, true);

				/*
				 * 我们假设第一列运算符的名称也适用于其余部分。这肯定是有失败的可能性，
				 * 例如，如果自构造解析以来某些但并非全部运算符被重命名，
				 * 但似乎没有办法做到完美。
				 */
				appendStringInfo(fc_buf, ") %s ROW(",
								 fc_generate_operator_name(linitial_oid(fc_rcexpr->opnos),
														exprType(linitial(fc_rcexpr->largs)),
														exprType(linitial(fc_rcexpr->rargs))));
				get_rule_list_toplevel(fc_rcexpr->rargs, fc_context, true);
				appendStringInfoString(fc_buf, "))");
			}
			break;

		case T_CoalesceExpr:
			{
				CoalesceExpr *fc_coalesceexpr = (CoalesceExpr *) fc_node;

				appendStringInfoString(fc_buf, "COALESCE(");
				get_rule_expr((Node *) fc_coalesceexpr->args, fc_context, true);
				appendStringInfoChar(fc_buf, ')');
			}
			break;

		case T_MinMaxExpr:
			{
				MinMaxExpr *fc_minmaxexpr = (MinMaxExpr *) fc_node;

				switch (fc_minmaxexpr->op)
				{
					case IS_GREATEST:
						appendStringInfoString(fc_buf, "GREATEST(");
						break;
					case IS_LEAST:
						appendStringInfoString(fc_buf, "LEAST(");
						break;
				}
				get_rule_expr((Node *) fc_minmaxexpr->args, fc_context, true);
				appendStringInfoChar(fc_buf, ')');
			}
			break;

		case T_SQLValueFunction:
			{
				SQLValueFunction *fc_svf = (SQLValueFunction *) fc_node;

				/*
				 * 注意：此代码知道 time、timestamp 和 timestamptz 的 typmod
				 * 仅以整数形式打印。
				 */
				switch (fc_svf->op)
				{
					case SVFOP_CURRENT_DATE:
						appendStringInfoString(fc_buf, "CURRENT_DATE");
						break;
					case SVFOP_CURRENT_TIME:
						appendStringInfoString(fc_buf, "CURRENT_TIME");
						break;
					case SVFOP_CURRENT_TIME_N:
						appendStringInfo(fc_buf, "CURRENT_TIME(%d)", fc_svf->typmod);
						break;
					case SVFOP_CURRENT_TIMESTAMP:
						appendStringInfoString(fc_buf, "CURRENT_TIMESTAMP");
						break;
					case SVFOP_CURRENT_TIMESTAMP_N:
						appendStringInfo(fc_buf, "CURRENT_TIMESTAMP(%d)",
										 fc_svf->typmod);
						break;
					case SVFOP_LOCALTIME:
						appendStringInfoString(fc_buf, "LOCALTIME");
						break;
					case SVFOP_LOCALTIME_N:
						appendStringInfo(fc_buf, "LOCALTIME(%d)", fc_svf->typmod);
						break;
					case SVFOP_LOCALTIMESTAMP:
						appendStringInfoString(fc_buf, "LOCALTIMESTAMP");
						break;
					case SVFOP_LOCALTIMESTAMP_N:
						appendStringInfo(fc_buf, "LOCALTIMESTAMP(%d)",
										 fc_svf->typmod);
						break;
					case SVFOP_CURRENT_ROLE:
						appendStringInfoString(fc_buf, "CURRENT_ROLE");
						break;
					case SVFOP_CURRENT_USER:
						appendStringInfoString(fc_buf, "CURRENT_USER");
						break;
					case SVFOP_USER:
						appendStringInfoString(fc_buf, "USER");
						break;
					case SVFOP_SESSION_USER:
						appendStringInfoString(fc_buf, "SESSION_USER");
						break;
					case SVFOP_CURRENT_CATALOG:
						appendStringInfoString(fc_buf, "CURRENT_CATALOG");
						break;
					case SVFOP_CURRENT_SCHEMA:
						appendStringInfoString(fc_buf, "CURRENT_SCHEMA");
						break;
				}
			}
			break;

		case T_XmlExpr:
			{
				XmlExpr    *fc_xexpr = (XmlExpr *) fc_node;
				bool		fc_needcomma = false;
				ListCell   *fc_arg;
				ListCell   *fc_narg;
				Const	   *fc_con;

				switch (fc_xexpr->op)
				{
					case IS_XMLCONCAT:
						appendStringInfoString(fc_buf, "XMLCONCAT(");
						break;
					case IS_XMLELEMENT:
						appendStringInfoString(fc_buf, "XMLELEMENT(");
						break;
					case IS_XMLFOREST:
						appendStringInfoString(fc_buf, "XMLFOREST(");
						break;
					case IS_XMLPARSE:
						appendStringInfoString(fc_buf, "XMLPARSE(");
						break;
					case IS_XMLPI:
						appendStringInfoString(fc_buf, "XMLPI(");
						break;
					case IS_XMLROOT:
						appendStringInfoString(fc_buf, "XMLROOT(");
						break;
					case IS_XMLSERIALIZE:
						appendStringInfoString(fc_buf, "XMLSERIALIZE(");
						break;
					case IS_DOCUMENT:
						break;
				}
				if (fc_xexpr->op == IS_XMLPARSE || fc_xexpr->op == IS_XMLSERIALIZE)
				{
					if (fc_xexpr->xmloption == XMLOPTION_DOCUMENT)
						appendStringInfoString(fc_buf, "DOCUMENT ");
					else
						appendStringInfoString(fc_buf, "CONTENT ");
				}
				if (fc_xexpr->name)
				{
					appendStringInfo(fc_buf, "NAME %s",
									 quote_identifier(map_xml_name_to_sql_identifier(fc_xexpr->name)));
					fc_needcomma = true;
				}
				if (fc_xexpr->named_args)
				{
					if (fc_xexpr->op != IS_XMLFOREST)
					{
						if (fc_needcomma)
							appendStringInfoString(fc_buf, ", ");
						appendStringInfoString(fc_buf, "XMLATTRIBUTES(");
						fc_needcomma = false;
					}
					forboth(fc_arg, fc_xexpr->named_args, fc_narg, fc_xexpr->arg_names)
					{
						Node	   *fc_e = (Node *) lfirst(fc_arg);
						char	   *fc_argname = strVal(lfirst(fc_narg));

						if (fc_needcomma)
							appendStringInfoString(fc_buf, ", ");
						get_rule_expr((Node *) fc_e, fc_context, true);
						appendStringInfo(fc_buf, " AS %s",
										 quote_identifier(map_xml_name_to_sql_identifier(fc_argname)));
						fc_needcomma = true;
					}
					if (fc_xexpr->op != IS_XMLFOREST)
						appendStringInfoChar(fc_buf, ')');
				}
				if (fc_xexpr->args)
				{
					if (fc_needcomma)
						appendStringInfoString(fc_buf, ", ");
					switch (fc_xexpr->op)
					{
						case IS_XMLCONCAT:
						case IS_XMLELEMENT:
						case IS_XMLFOREST:
						case IS_XMLPI:
						case IS_XMLSERIALIZE:
							/* 不需要额外的装饰 */
							get_rule_expr((Node *) fc_xexpr->args, fc_context, true);
							break;
						case IS_XMLPARSE:
							Assert(list_length(fc_xexpr->args) == 2);

							get_rule_expr((Node *) linitial(fc_xexpr->args),
										  fc_context, true);

							fc_con = lsecond_node(Const, fc_xexpr->args);
							Assert(!fc_con->constisnull);
							if (DatumGetBool(fc_con->constvalue))
								appendStringInfoString(fc_buf,
													   " PRESERVE WHITESPACE");
							else
								appendStringInfoString(fc_buf,
													   " STRIP WHITESPACE");
							break;
						case IS_XMLROOT:
							Assert(list_length(fc_xexpr->args) == 3);

							get_rule_expr((Node *) linitial(fc_xexpr->args),
										  fc_context, true);

							appendStringInfoString(fc_buf, ", VERSION ");
							fc_con = (Const *) lsecond(fc_xexpr->args);
							if (IsA(fc_con, Const) &&
								fc_con->constisnull)
								appendStringInfoString(fc_buf, "NO VALUE");
							else
								get_rule_expr((Node *) fc_con, fc_context, false);

							fc_con = lthird_node(Const, fc_xexpr->args);
							if (fc_con->constisnull)
								 /* 抑制 STANDALONE NO VALUE */ ;
							else
							{
								switch (DatumGetInt32(fc_con->constvalue))
								{
									case XML_STANDALONE_YES:
										appendStringInfoString(fc_buf,
															   ", STANDALONE YES");
										break;
									case XML_STANDALONE_NO:
										appendStringInfoString(fc_buf,
															   ", STANDALONE NO");
										break;
									case XML_STANDALONE_NO_VALUE:
										appendStringInfoString(fc_buf,
															   ", STANDALONE NO VALUE");
										break;
									default:
										break;
								}
							}
							break;
						case IS_DOCUMENT:
							get_rule_expr_paren((Node *) fc_xexpr->args, fc_context, false, fc_node);
							break;
					}
				}
				if (fc_xexpr->op == IS_XMLSERIALIZE)
					appendStringInfo(fc_buf, " AS %s",
									 format_type_with_typemod(fc_xexpr->type,
															  fc_xexpr->typmod));
				if (fc_xexpr->op == IS_DOCUMENT)
					appendStringInfoString(fc_buf, " IS DOCUMENT");
				else
					appendStringInfoChar(fc_buf, ')');
			}
			break;

		case T_NullTest:
			{
				NullTest   *fc_ntest = (NullTest *) fc_node;

				if (!PRETTY_PAREN(fc_context))
					appendStringInfoChar(fc_buf, '(');
				get_rule_expr_paren((Node *) fc_ntest->arg, fc_context, true, fc_node);

				/*
				 * 对于标量输入，我们更倾向于打印为 IS [NOT] NULL，
				 * 这更简短且更为传统。如果它是 rowtype 输入，但我们应用标量测试，
				 * 必须打印 IS [NOT] DISTINCT FROM NULL 才能在语义上正确。
				 */
				if (fc_ntest->argisrow ||
					!type_is_rowtype(exprType((Node *) fc_ntest->arg)))
				{
					switch (fc_ntest->nulltesttype)
					{
						case IS_NULL:
							appendStringInfoString(fc_buf, " IS NULL");
							break;
						case IS_NOT_NULL:
							appendStringInfoString(fc_buf, " IS NOT NULL");
							break;
						default:
							elog(ERROR, "unrecognized nulltesttype: %d",
								 (int) fc_ntest->nulltesttype);
					}
				}
				else
				{
					switch (fc_ntest->nulltesttype)
					{
						case IS_NULL:
							appendStringInfoString(fc_buf, " IS NOT DISTINCT FROM NULL");
							break;
						case IS_NOT_NULL:
							appendStringInfoString(fc_buf, " IS DISTINCT FROM NULL");
							break;
						default:
							elog(ERROR, "unrecognized nulltesttype: %d",
								 (int) fc_ntest->nulltesttype);
					}
				}
				if (!PRETTY_PAREN(fc_context))
					appendStringInfoChar(fc_buf, ')');
			}
			break;

		case T_BooleanTest:
			{
				BooleanTest *fc_btest = (BooleanTest *) fc_node;

				if (!PRETTY_PAREN(fc_context))
					appendStringInfoChar(fc_buf, '(');
				get_rule_expr_paren((Node *) fc_btest->arg, fc_context, false, fc_node);
				switch (fc_btest->booltesttype)
				{
					case IS_TRUE:
						appendStringInfoString(fc_buf, " IS TRUE");
						break;
					case IS_NOT_TRUE:
						appendStringInfoString(fc_buf, " IS NOT TRUE");
						break;
					case IS_FALSE:
						appendStringInfoString(fc_buf, " IS FALSE");
						break;
					case IS_NOT_FALSE:
						appendStringInfoString(fc_buf, " IS NOT FALSE");
						break;
					case IS_UNKNOWN:
						appendStringInfoString(fc_buf, " IS UNKNOWN");
						break;
					case IS_NOT_UNKNOWN:
						appendStringInfoString(fc_buf, " IS NOT UNKNOWN");
						break;
					default:
						elog(ERROR, "unrecognized booltesttype: %d",
							 (int) fc_btest->booltesttype);
				}
				if (!PRETTY_PAREN(fc_context))
					appendStringInfoChar(fc_buf, ')');
			}
			break;

		case T_CoerceToDomain:
			{
				CoerceToDomain *fc_ctest = (CoerceToDomain *) fc_node;
				Node	   *fc_arg = (Node *) fc_ctest->arg;

				if (fc_ctest->coercionformat == COERCE_IMPLICIT_CAST &&
					!fc_showimplicit)
				{
					/* 不显示隐式转换 */
					get_rule_expr(fc_arg, fc_context, false);
				}
				else
				{
					get_coercion_expr(fc_arg, fc_context,
									  fc_ctest->resulttype,
									  fc_ctest->resulttypmod,
									  fc_node);
				}
			}
			break;

		case T_CoerceToDomainValue:
			appendStringInfoString(fc_buf, "VALUE");
			break;

		case T_SetToDefault:
			appendStringInfoString(fc_buf, "DEFAULT");
			break;

		case T_CurrentOfExpr:
			{
				CurrentOfExpr *fc_cexpr = (CurrentOfExpr *) fc_node;

				if (fc_cexpr->cursor_name)
					appendStringInfo(fc_buf, "CURRENT OF %s",
									 quote_identifier(fc_cexpr->cursor_name));
				else
					appendStringInfo(fc_buf, "CURRENT OF $%d",
									 fc_cexpr->cursor_param);
			}
			break;

		case T_NextValueExpr:
			{
				NextValueExpr *fc_nvexpr = (NextValueExpr *) fc_node;

				/*
				 * 这并不完全是 nextval()，但似乎足够接近 EXPLAIN 的目的。
				 */
				appendStringInfoString(fc_buf, "nextval(");
				fc_simple_quote_literal(fc_buf,
									 fc_generate_relation_name(fc_nvexpr->seqid,
															NIL));
				appendStringInfoChar(fc_buf, ')');
			}
			break;

		case T_InferenceElem:
			{
				InferenceElem *fc_iexpr = (InferenceElem *) fc_node;
				bool		fc_save_varprefix;
				bool		fc_need_parens;

				/*
				 * InferenceElem 只能引用目标关系，因此前缀没有用处，确实会导致解析错误。
				 */
				fc_save_varprefix = fc_context->varprefix;
				fc_context->varprefix = false;

				/*
				 * 除非它是简单的 Var 或裸函数调用，否则将元素括起来。遵循 pg_get_indexdef_worker()。
				 */
				fc_need_parens = !IsA(fc_iexpr->expr, Var);
				if (IsA(fc_iexpr->expr, FuncExpr) &&
					((FuncExpr *) fc_iexpr->expr)->funcformat ==
					COERCE_EXPLICIT_CALL)
					fc_need_parens = false;

				if (fc_need_parens)
					appendStringInfoChar(fc_buf, '(');
				get_rule_expr((Node *) fc_iexpr->expr,
							  fc_context, false);
				if (fc_need_parens)
					appendStringInfoChar(fc_buf, ')');

				fc_context->varprefix = fc_save_varprefix;

				if (fc_iexpr->infercollid)
					appendStringInfo(fc_buf, " COLLATE %s",
									 generate_collation_name(fc_iexpr->infercollid));

				/* 如果不是默认，则添加操作符类名称 */
				if (fc_iexpr->inferopclass)
				{
					Oid			fc_inferopclass = fc_iexpr->inferopclass;
					Oid			fc_inferopcinputtype = get_opclass_input_type(fc_iexpr->inferopclass);

					get_opclass_name(fc_inferopclass, fc_inferopcinputtype, fc_buf);
				}
			}
			break;

		case T_PartitionBoundSpec:
			{
				PartitionBoundSpec *fc_spec = (PartitionBoundSpec *) fc_node;
				ListCell   *fc_cell;
				char	   *fc_sep;

				if (fc_spec->is_default)
				{
					appendStringInfoString(fc_buf, "DEFAULT");
					break;
				}

				switch (fc_spec->strategy)
				{
					case PARTITION_STRATEGY_HASH:
						Assert(fc_spec->modulus > 0 && fc_spec->remainder >= 0);
						Assert(fc_spec->modulus > fc_spec->remainder);

						appendStringInfoString(fc_buf, "FOR VALUES");
						appendStringInfo(fc_buf, " WITH (modulus %d, remainder %d)",
										 fc_spec->modulus, fc_spec->remainder);
						break;

					case PARTITION_STRATEGY_LIST:
						Assert(fc_spec->listdatums != NIL);

						appendStringInfoString(fc_buf, "FOR VALUES IN (");
						fc_sep = "";
						foreach(fc_cell, fc_spec->listdatums)
						{
							Const	   *fc_val = lfirst_node(Const, fc_cell);

							appendStringInfoString(fc_buf, fc_sep);
							get_const_expr(fc_val, fc_context, -1);
							fc_sep = ", ";
						}

						appendStringInfoChar(fc_buf, ')');
						break;

					case PARTITION_STRATEGY_RANGE:
						Assert(fc_spec->lowerdatums != NIL &&
							   fc_spec->upperdatums != NIL &&
							   list_length(fc_spec->lowerdatums) ==
							   list_length(fc_spec->upperdatums));

						appendStringInfo(fc_buf, "FOR VALUES FROM %s TO %s",
										 get_range_partbound_string(fc_spec->lowerdatums),
										 get_range_partbound_string(fc_spec->upperdatums));
						break;

					default:
						elog(ERROR, "unrecognized partition strategy: %d",
							 (int) fc_spec->strategy);
						break;
				}
			}
			break;

		case T_List:
			{
				char	   *fc_sep;
				ListCell   *fc_l;

				fc_sep = "";
				foreach(fc_l, (List *) fc_node)
				{
					appendStringInfoString(fc_buf, fc_sep);
					get_rule_expr((Node *) lfirst(fc_l), fc_context, fc_showimplicit);
					fc_sep = ", ";
				}
			}
			break;

		case T_TableFunc:
			get_tablefunc((TableFunc *) fc_node, fc_context, fc_showimplicit);
			break;

		default:
			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(fc_node));
			break;
	}
}

/*
 * get_rule_expr_toplevel - 解析回顶层表达式
 *
 * 与 get_rule_expr() 相同，除了如果表达式只是 Var，我们将 istoplevel = true 而不是 false 传递给 get_variable()。这会导致整个行变量以装饰形式打印，从而防止“*”的扩展。我们需要在 ROW() 和 VALUES() 等上下文中使用此功能，在这些上下文中，解析器将扩展顶层出现的“foo.*”。（原则上我们也会在 get_target_list() 中使用这个，但它有额外的担忧，关于是否打印 AS，因此无论如何需要直接调用 get_variable()。）
 */
static void get_rule_expr_toplevel(Node *fc_node, deparse_context *fc_context,
					   bool fc_showimplicit)
{
	if (fc_node && IsA(fc_node, Var))
		(void) get_variable((Var *) fc_node, 0, true, fc_context);
	else
		get_rule_expr(fc_node, fc_context, fc_showimplicit);
}

/*
 * get_rule_list_toplevel - 解析回顶层表达式列表
 *
 * 对列表的每个元素应用 get_rule_expr_toplevel()。
 *
 * 这在表达式之间添加逗号，但调用者负责打印周围的装饰。
 */
static void get_rule_list_toplevel(List *fc_lst, deparse_context *fc_context,
					   bool fc_showimplicit)
{
	const char *fc_sep;
	ListCell   *fc_lc;

	fc_sep = "";
	foreach(fc_lc, fc_lst)
	{
		Node	   *fc_e = (Node *) lfirst(fc_lc);

		appendStringInfoString(fc_context->buf, fc_sep);
		get_rule_expr_toplevel(fc_e, fc_context, fc_showimplicit);
		fc_sep = ", ";
	}
}

/*
 * get_rule_expr_funccall - 解析回函数调用表达式
 *
 * 与 get_rule_expr() 相同，除了我们保证输出看起来像函数调用，或像语法将其视作函数调用的某些东西（见 func_expr_windowless 产生）。在语法使用 func_expr_windowless 并且您无法替换括号中的 a_expr 的地方，这一点是必要的。如果我们得到的东西看起来不会像函数调用，则将其包装在一个虚拟的 CAST() 表达式中，这将满足语法——实际上，这很可能是用户为生成这种东西而编写的内容。
 */
static void get_rule_expr_funccall(Node *fc_node, deparse_context *fc_context,
					   bool fc_showimplicit)
{
	if (fc_looks_like_function(fc_node))
		get_rule_expr(fc_node, fc_context, fc_showimplicit);
	else
	{
		StringInfo	fc_buf = fc_context->buf;

		appendStringInfoString(fc_buf, "CAST(");
		/* 显示任何顶层隐式转换是没有意义的 */
		get_rule_expr(fc_node, fc_context, false);
		appendStringInfo(fc_buf, " AS %s)",
						 format_type_with_typemod(exprType(fc_node),
												  exprTypmod(fc_node)));
	}
}

/*
 * 辅助函数，用于识别满足 func_expr_windowless 的节点类型。
 * 如果有疑问，“false”始终是一个安全的答案。
 */
static bool fc_looks_like_function(Node *fc_node)
{
	if (fc_node == NULL)
		return false;			/* 这可能不应该发生 */
	switch (nodeTag(fc_node))
	{
		case T_FuncExpr:
			/* 好的，除非它将反解析为一个转换 */
			return (((FuncExpr *) fc_node)->funcformat == COERCE_EXPLICIT_CALL ||
					((FuncExpr *) fc_node)->funcformat == COERCE_SQL_SYNTAX);
		case T_NullIfExpr:
		case T_CoalesceExpr:
		case T_MinMaxExpr:
		case T_SQLValueFunction:
		case T_XmlExpr:
			/* 这些都是 func_expr_common_subexpr 所接受的 */
			return true;
		default:
			break;
	}
	return false;
}



/*
 * get_oper_expr			- 解析一个 OpExpr 节点
 */
static void get_oper_expr(OpExpr *fc_expr, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	Oid			fc_opno = fc_expr->opno;
	List	   *fc_args = fc_expr->args;

	if (!PRETTY_PAREN(fc_context))
		appendStringInfoChar(fc_buf, '(');
	if (list_length(fc_args) == 2)
	{
		/* 二元运算符 */
		Node	   *fc_arg1 = (Node *) linitial(fc_args);
		Node	   *fc_arg2 = (Node *) lsecond(fc_args);

		get_rule_expr_paren(fc_arg1, fc_context, true, (Node *) fc_expr);
		appendStringInfo(fc_buf, " %s ",
						 fc_generate_operator_name(fc_opno,
												exprType(fc_arg1),
												exprType(fc_arg2)));
		get_rule_expr_paren(fc_arg2, fc_context, true, (Node *) fc_expr);
	}
	else
	{
		/* 前缀运算符 */
		Node	   *fc_arg = (Node *) linitial(fc_args);

		appendStringInfo(fc_buf, "%s ",
						 fc_generate_operator_name(fc_opno,
												InvalidOid,
												exprType(fc_arg)));
		get_rule_expr_paren(fc_arg, fc_context, true, (Node *) fc_expr);
	}
	if (!PRETTY_PAREN(fc_context))
		appendStringInfoChar(fc_buf, ')');
}

/*
 * get_func_expr			- 解析一个 FuncExpr 节点
 */
static void get_func_expr(FuncExpr *fc_expr, deparse_context *fc_context,
			  bool fc_showimplicit)
{
	StringInfo	fc_buf = fc_context->buf;
	Oid			fc_funcoid = fc_expr->funcid;
	Oid			fc_argtypes[FUNC_MAX_ARGS];
	int			fc_nargs;
	List	   *fc_argnames;
	bool		fc_use_variadic;
	ListCell   *fc_l;

	/*
	 * 如果函数调用来自隐式强制转换，则仅显示第一个参数 --- 除非调用者希望查看隐式强制转换。
	 */
	if (fc_expr->funcformat == COERCE_IMPLICIT_CAST && !fc_showimplicit)
	{
		get_rule_expr_paren((Node *) linitial(fc_expr->args), fc_context,
							false, (Node *) fc_expr);
		return;
	}

	/* 如果这是一个长度强制转换函数，则获取 typmod */
	if (fc_expr->funcformat == COERCE_EXPLICIT_CAST ||
		fc_expr->funcformat == COERCE_IMPLICIT_CAST)
	{
		Node	   *fc_arg = linitial(fc_expr->args);
		Oid			fc_rettype = fc_expr->funcresulttype;
		int32		fc_coercedTypmod;

		/* 检查是否需要打印 VARIADIC（参见 ruleutils.c） */
		(void) exprIsLengthCoercion((Node *) fc_expr, &fc_coercedTypmod);

		get_coercion_expr(fc_arg, fc_context,
						  fc_rettype, fc_coercedTypmod,
						  (Node *) fc_expr);

		return;
	}

	/*
	 * 如果函数是使用 SQL 规范的随机特殊语法调用的，尝试重现该语法。 
	 * 如果我们不认识该函数，则继续执行。
	 */
	if (fc_expr->funcformat == COERCE_SQL_SYNTAX)
	{
		if (get_func_sql_syntax(fc_expr, fc_context))
			return;
	}

	/*
	 * 正常函数：显示为 proname(args)。首先，我们需要提取
	 * 参数数据类型。
	 */
	if (list_length(fc_expr->args) > FUNC_MAX_ARGS)
		ereport(ERROR,
				(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
				 errmsg("too many arguments")));
	fc_nargs = 0;
	fc_argnames = NIL;
	foreach(fc_l, fc_expr->args)
	{
		Node	   *fc_arg = (Node *) lfirst(fc_l);

		if (IsA(fc_arg, NamedArgExpr))
			fc_argnames = lappend(fc_argnames, ((NamedArgExpr *) fc_arg)->name);
		fc_argtypes[fc_nargs] = exprType(fc_arg);
		fc_nargs++;
	}

	appendStringInfo(fc_buf, "%s(",
					 fc_generate_function_name(fc_funcoid, fc_nargs,
											fc_argnames, fc_argtypes,
											fc_expr->funcvariadic,
											&fc_use_variadic,
											fc_context->special_exprkind));
	fc_nargs = 0;
	foreach(fc_l, fc_expr->args)
	{
		if (fc_nargs++ > 0)
			appendStringInfoString(fc_buf, ", ");
		if (fc_use_variadic && lnext(fc_expr->args, fc_l) == NULL)
			appendStringInfoString(fc_buf, "VARIADIC ");
		get_rule_expr((Node *) lfirst(fc_l), fc_context, true);
	}
	appendStringInfoChar(fc_buf, ')');
}

/*
 * get_agg_expr			- 解析 Aggref 节点
 */
static void get_agg_expr(Aggref *fc_aggref, deparse_context *fc_context,
			 Aggref *fc_original_aggref)
{
	StringInfo	fc_buf = fc_context->buf;
	Oid			fc_argtypes[FUNC_MAX_ARGS];
	int			fc_nargs;
	bool		fc_use_variadic;

	/*
	 * 对于组合聚合，我们查找并解析相应的
	 * 部分聚合。这样做是必要的，因为我们的输入参数列表已被替换；
	 * 新的参数列表总是只有一个元素，它将指向一个部分 Aggref，提供我们
	 * 合并的过渡状态。
	 */
	if (DO_AGGSPLIT_COMBINE(fc_aggref->aggsplit))
	{
		TargetEntry *fc_tle;

		Assert(list_length(fc_aggref->args) == 1);
		fc_tle = linitial_node(TargetEntry, fc_aggref->args);
		fc_resolve_special_varno((Node *) fc_tle->expr, fc_context,
							  get_agg_combine_expr, fc_original_aggref);
		return;
	}

	/*
	 * 如果合适，则标记为 PARTIAL。我们查看原始群聚，以避免在
	 * 从上面的代码中递归时打印此信息。
	 */
	if (DO_AGGSPLIT_SKIPFINAL(fc_original_aggref->aggsplit))
		appendStringInfoString(fc_buf, "PARTIAL ");

	/* 提取解析器看到的参数类型 */
	fc_nargs = get_aggregate_argtypes(fc_aggref, fc_argtypes);

	/* 打印聚合名称，必要时带模式资格 */
	appendStringInfo(fc_buf, "%s(%s",
					 fc_generate_function_name(fc_aggref->aggfnoid, fc_nargs,
											NIL, fc_argtypes,
											fc_aggref->aggvariadic,
											&fc_use_variadic,
											fc_context->special_exprkind),
					 (fc_aggref->aggdistinct != NIL) ? "DISTINCT " : "");

	if (AGGKIND_IS_ORDERED_SET(fc_aggref->aggkind))
	{
		/*
		 * 有序集合聚合不使用 "*" 语法。此外，我们不需要担心插入
		 * VARIADIC。因此，我们可以直接输出参数。
		 */
		Assert(!fc_aggref->aggvariadic);
		get_rule_expr((Node *) fc_aggref->aggdirectargs, fc_context, true);
		Assert(fc_aggref->aggorder != NIL);
		appendStringInfoString(fc_buf, ") WITHIN GROUP (ORDER BY ");
		get_rule_orderby(fc_aggref->aggorder, fc_aggref->args, false, fc_context);
	}
	else
	{
		/* 添加所有参数 */
		if (fc_aggref->aggstar)
			appendStringInfoChar(fc_buf, '*');
		else
		{
			ListCell   *fc_l;
			int			fc_i;

			fc_i = 0;
			foreach(fc_l, fc_aggref->args)
			{
				TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_l);
				Node	   *fc_arg = (Node *) fc_tle->expr;

				Assert(!IsA(fc_arg, NamedArgExpr));
				if (fc_tle->resjunk)
					continue;
				if (fc_i++ > 0)
					appendStringInfoString(fc_buf, ", ");
				if (fc_use_variadic && fc_i == fc_nargs)
					appendStringInfoString(fc_buf, "VARIADIC ");
				get_rule_expr(fc_arg, fc_context, true);
			}
		}

		if (fc_aggref->aggorder != NIL)
		{
			appendStringInfoString(fc_buf, " ORDER BY ");
			get_rule_orderby(fc_aggref->aggorder, fc_aggref->args, false, fc_context);
		}
	}

	if (fc_aggref->aggfilter != NULL)
	{
		appendStringInfoString(fc_buf, ") FILTER (WHERE ");
		get_rule_expr((Node *) fc_aggref->aggfilter, fc_context, false);
	}

	appendStringInfoChar(fc_buf, ')');
}

/*
 * 这是 get_agg_expr() 的辅助函数。当我们解析一个组合 Aggref 时使用； 
 * resolve_special_varno 定位相应的部分 Aggref，然后调用该函数。
 */
static void get_agg_combine_expr(Node *fc_node, deparse_context *fc_context, void *fc_callback_arg)
{
	Aggref	   *fc_aggref;
	Aggref	   *fc_original_aggref = fc_callback_arg;

	if (!IsA(fc_node, Aggref))
		elog(ERROR, "combining Aggref does not point to an Aggref");

	fc_aggref = (Aggref *) fc_node;
	get_agg_expr(fc_aggref, fc_context, fc_original_aggref);
}

/*
 * get_windowfunc_expr	- 解析 WindowFunc 节点
 */
static void get_windowfunc_expr(WindowFunc *fc_wfunc, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	Oid			fc_argtypes[FUNC_MAX_ARGS];
	int			fc_nargs;
	List	   *fc_argnames;
	ListCell   *fc_l;

	if (list_length(fc_wfunc->args) > FUNC_MAX_ARGS)
		ereport(ERROR,
				(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
				 errmsg("too many arguments")));
	fc_nargs = 0;
	fc_argnames = NIL;
	foreach(fc_l, fc_wfunc->args)
	{
		Node	   *fc_arg = (Node *) lfirst(fc_l);

		if (IsA(fc_arg, NamedArgExpr))
			fc_argnames = lappend(fc_argnames, ((NamedArgExpr *) fc_arg)->name);
		fc_argtypes[fc_nargs] = exprType(fc_arg);
		fc_nargs++;
	}

	appendStringInfo(fc_buf, "%s(",
					 fc_generate_function_name(fc_wfunc->winfnoid, fc_nargs,
											fc_argnames, fc_argtypes,
											false, NULL,
											fc_context->special_exprkind));
	/* winstar 只能在零参数聚合中设置 */
	if (fc_wfunc->winstar)
		appendStringInfoChar(fc_buf, '*');
	else
		get_rule_expr((Node *) fc_wfunc->args, fc_context, true);

	if (fc_wfunc->aggfilter != NULL)
	{
		appendStringInfoString(fc_buf, ") FILTER (WHERE ");
		get_rule_expr((Node *) fc_wfunc->aggfilter, fc_context, false);
	}

	appendStringInfoString(fc_buf, ") OVER ");

	foreach(fc_l, fc_context->windowClause)
	{
		WindowClause *fc_wc = (WindowClause *) lfirst(fc_l);

		if (fc_wc->winref == fc_wfunc->winref)
		{
			if (fc_wc->name)
				appendStringInfoString(fc_buf, quote_identifier(fc_wc->name));
			else
				get_rule_windowspec(fc_wc, fc_context->windowTList, fc_context);
			break;
		}
	}
	if (fc_l == NULL)
	{
		if (fc_context->windowClause)
			elog(ERROR, "could not find window clause for winref %u",
				 fc_wfunc->winref);

		/*
		 * 在 EXPLAIN 中，我们没有可用的窗口上下文信息，
		 * 所以我们只能满足于此：
		 */
		appendStringInfoString(fc_buf, "(?)");
	}
}

/*
 * get_func_sql_syntax		- 解析 SQL 语法函数调用
 *
 * 如果我们成功解析，则返回 true；如果我们没有
 * 识别该函数，则返回 false。
 */
static bool get_func_sql_syntax(FuncExpr *fc_expr, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	Oid			fc_funcoid = fc_expr->funcid;

	switch (fc_funcoid)
	{
		case F_TIMEZONE_INTERVAL_TIMESTAMP:
		case F_TIMEZONE_INTERVAL_TIMESTAMPTZ:
		case F_TIMEZONE_INTERVAL_TIMETZ:
		case F_TIMEZONE_TEXT_TIMESTAMP:
		case F_TIMEZONE_TEXT_TIMESTAMPTZ:
		case F_TIMEZONE_TEXT_TIMETZ:
			/* AT TIME ZONE ... 注意参数顺序反转 */
			appendStringInfoChar(fc_buf, '(');
			get_rule_expr_paren((Node *) lsecond(fc_expr->args), fc_context, false,
								(Node *) fc_expr);
			appendStringInfoString(fc_buf, " AT TIME ZONE ");
			get_rule_expr_paren((Node *) linitial(fc_expr->args), fc_context, false,
								(Node *) fc_expr);
			appendStringInfoChar(fc_buf, ')');
			return true;

		case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_INTERVAL:
		case F_OVERLAPS_TIMESTAMPTZ_INTERVAL_TIMESTAMPTZ_TIMESTAMPTZ:
		case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_INTERVAL:
		case F_OVERLAPS_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ_TIMESTAMPTZ:
		case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_INTERVAL:
		case F_OVERLAPS_TIMESTAMP_INTERVAL_TIMESTAMP_TIMESTAMP:
		case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_INTERVAL:
		case F_OVERLAPS_TIMESTAMP_TIMESTAMP_TIMESTAMP_TIMESTAMP:
		case F_OVERLAPS_TIMETZ_TIMETZ_TIMETZ_TIMETZ:
		case F_OVERLAPS_TIME_INTERVAL_TIME_INTERVAL:
		case F_OVERLAPS_TIME_INTERVAL_TIME_TIME:
		case F_OVERLAPS_TIME_TIME_TIME_INTERVAL:
		case F_OVERLAPS_TIME_TIME_TIME_TIME:
			/* (x1, x2) OVERLAPS (y1, y2) */
			appendStringInfoString(fc_buf, "((");
			get_rule_expr((Node *) linitial(fc_expr->args), fc_context, false);
			appendStringInfoString(fc_buf, ", ");
			get_rule_expr((Node *) lsecond(fc_expr->args), fc_context, false);
			appendStringInfoString(fc_buf, ") OVERLAPS (");
			get_rule_expr((Node *) lthird(fc_expr->args), fc_context, false);
			appendStringInfoString(fc_buf, ", ");
			get_rule_expr((Node *) lfourth(fc_expr->args), fc_context, false);
			appendStringInfoString(fc_buf, "))");
			return true;

		case F_EXTRACT_TEXT_DATE:
		case F_EXTRACT_TEXT_TIME:
		case F_EXTRACT_TEXT_TIMETZ:
		case F_EXTRACT_TEXT_TIMESTAMP:
		case F_EXTRACT_TEXT_TIMESTAMPTZ:
		case F_EXTRACT_TEXT_INTERVAL:
			/* EXTRACT (x FROM y) */
			appendStringInfoString(fc_buf, "EXTRACT(");
			{
				Const	   *fc_con = (Const *) linitial(fc_expr->args);

				Assert(IsA(fc_con, Const) &&
					   fc_con->consttype == TEXTOID &&
					   !fc_con->constisnull);
				appendStringInfoString(fc_buf, TextDatumGetCString(fc_con->constvalue));
			}
			appendStringInfoString(fc_buf, " FROM ");
			get_rule_expr((Node *) lsecond(fc_expr->args), fc_context, false);
			appendStringInfoChar(fc_buf, ')');
			return true;

		case F_IS_NORMALIZED:
			/* IS xxx NORMALIZED */
			appendStringInfoString(fc_buf, "(");
			get_rule_expr_paren((Node *) linitial(fc_expr->args), fc_context, false,
								(Node *) fc_expr);
			appendStringInfoString(fc_buf, " IS");
			if (list_length(fc_expr->args) == 2)
			{
				Const	   *fc_con = (Const *) lsecond(fc_expr->args);

				Assert(IsA(fc_con, Const) &&
					   fc_con->consttype == TEXTOID &&
					   !fc_con->constisnull);
				appendStringInfo(fc_buf, " %s",
								 TextDatumGetCString(fc_con->constvalue));
			}
			appendStringInfoString(fc_buf, " NORMALIZED)");
			return true;

		case F_PG_COLLATION_FOR:
			/* COLLATION FOR */
			appendStringInfoString(fc_buf, "COLLATION FOR (");
			get_rule_expr((Node *) linitial(fc_expr->args), fc_context, false);
			appendStringInfoChar(fc_buf, ')');
			return true;

		case F_NORMALIZE:
			/* NORMALIZE() */
			appendStringInfoString(fc_buf, "NORMALIZE(");
			get_rule_expr((Node *) linitial(fc_expr->args), fc_context, false);
			if (list_length(fc_expr->args) == 2)
			{
				Const	   *fc_con = (Const *) lsecond(fc_expr->args);

				Assert(IsA(fc_con, Const) &&
					   fc_con->consttype == TEXTOID &&
					   !fc_con->constisnull);
				appendStringInfo(fc_buf, ", %s",
								 TextDatumGetCString(fc_con->constvalue));
			}
			appendStringInfoChar(fc_buf, ')');
			return true;

		case F_OVERLAY_BIT_BIT_INT4:
		case F_OVERLAY_BIT_BIT_INT4_INT4:
		case F_OVERLAY_BYTEA_BYTEA_INT4:
		case F_OVERLAY_BYTEA_BYTEA_INT4_INT4:
		case F_OVERLAY_TEXT_TEXT_INT4:
		case F_OVERLAY_TEXT_TEXT_INT4_INT4:
			/* OVERLAY() */
			appendStringInfoString(fc_buf, "OVERLAY(");
			get_rule_expr((Node *) linitial(fc_expr->args), fc_context, false);
			appendStringInfoString(fc_buf, " PLACING ");
			get_rule_expr((Node *) lsecond(fc_expr->args), fc_context, false);
			appendStringInfoString(fc_buf, " FROM ");
			get_rule_expr((Node *) lthird(fc_expr->args), fc_context, false);
			if (list_length(fc_expr->args) == 4)
			{
				appendStringInfoString(fc_buf, " FOR ");
				get_rule_expr((Node *) lfourth(fc_expr->args), fc_context, false);
			}
			appendStringInfoChar(fc_buf, ')');
			return true;

		case F_POSITION_BIT_BIT:
		case F_POSITION_BYTEA_BYTEA:
		case F_POSITION_TEXT_TEXT:
			/* POSITION() ... 由于参数是 b_expr 而不是 a_expr，因此有额外的括号 */
			appendStringInfoString(fc_buf, "POSITION((");
			get_rule_expr((Node *) lsecond(fc_expr->args), fc_context, false);
			appendStringInfoString(fc_buf, ") IN (");
			get_rule_expr((Node *) linitial(fc_expr->args), fc_context, false);
			appendStringInfoString(fc_buf, "))");
			return true;

		case F_SUBSTRING_BIT_INT4:
		case F_SUBSTRING_BIT_INT4_INT4:
		case F_SUBSTRING_BYTEA_INT4:
		case F_SUBSTRING_BYTEA_INT4_INT4:
		case F_SUBSTRING_TEXT_INT4:
		case F_SUBSTRING_TEXT_INT4_INT4:
			/* SUBSTRING FROM/FOR（即，整数位置变体） */
			appendStringInfoString(fc_buf, "SUBSTRING(");
			get_rule_expr((Node *) linitial(fc_expr->args), fc_context, false);
			appendStringInfoString(fc_buf, " FROM ");
			get_rule_expr((Node *) lsecond(fc_expr->args), fc_context, false);
			if (list_length(fc_expr->args) == 3)
			{
				appendStringInfoString(fc_buf, " FOR ");
				get_rule_expr((Node *) lthird(fc_expr->args), fc_context, false);
			}
			appendStringInfoChar(fc_buf, ')');
			return true;

		case F_SUBSTRING_TEXT_TEXT_TEXT:
			/* SUBSTRING SIMILAR/ESCAPE */
			appendStringInfoString(fc_buf, "SUBSTRING(");
			get_rule_expr((Node *) linitial(fc_expr->args), fc_context, false);
			appendStringInfoString(fc_buf, " SIMILAR ");
			get_rule_expr((Node *) lsecond(fc_expr->args), fc_context, false);
			appendStringInfoString(fc_buf, " ESCAPE ");
			get_rule_expr((Node *) lthird(fc_expr->args), fc_context, false);
			appendStringInfoChar(fc_buf, ')');
			return true;

		case F_BTRIM_BYTEA_BYTEA:
		case F_BTRIM_TEXT:
		case F_BTRIM_TEXT_TEXT:
			/* TRIM() */
			appendStringInfoString(fc_buf, "TRIM(BOTH");
			if (list_length(fc_expr->args) == 2)
			{
				appendStringInfoChar(fc_buf, ' ');
				get_rule_expr((Node *) lsecond(fc_expr->args), fc_context, false);
			}
			appendStringInfoString(fc_buf, " FROM ");
			get_rule_expr((Node *) linitial(fc_expr->args), fc_context, false);
			appendStringInfoChar(fc_buf, ')');
			return true;

		case F_LTRIM_BYTEA_BYTEA:
		case F_LTRIM_TEXT:
		case F_LTRIM_TEXT_TEXT:
			/* TRIM() */
			appendStringInfoString(fc_buf, "TRIM(LEADING");
			if (list_length(fc_expr->args) == 2)
			{
				appendStringInfoChar(fc_buf, ' ');
				get_rule_expr((Node *) lsecond(fc_expr->args), fc_context, false);
			}
			appendStringInfoString(fc_buf, " FROM ");
			get_rule_expr((Node *) linitial(fc_expr->args), fc_context, false);
			appendStringInfoChar(fc_buf, ')');
			return true;

		case F_RTRIM_BYTEA_BYTEA:
		case F_RTRIM_TEXT:
		case F_RTRIM_TEXT_TEXT:
			/* TRIM() */
			appendStringInfoString(fc_buf, "TRIM(TRAILING");
			if (list_length(fc_expr->args) == 2)
			{
				appendStringInfoChar(fc_buf, ' ');
				get_rule_expr((Node *) lsecond(fc_expr->args), fc_context, false);
			}
			appendStringInfoString(fc_buf, " FROM ");
			get_rule_expr((Node *) linitial(fc_expr->args), fc_context, false);
			appendStringInfoChar(fc_buf, ')');
			return true;

		case F_XMLEXISTS:
			/* XMLEXISTS ... 由于参数是 c_expr，因此有额外的括号 */
			appendStringInfoString(fc_buf, "XMLEXISTS((");
			get_rule_expr((Node *) linitial(fc_expr->args), fc_context, false);
			appendStringInfoString(fc_buf, ") PASSING (");
			get_rule_expr((Node *) lsecond(fc_expr->args), fc_context, false);
			appendStringInfoString(fc_buf, "))");
			return true;
	}
	return false;
}

/* ----------
 * get_coercion_expr
 *
 * 生成强制转换为特定类型的值的字符串表示
 * ----------
 */
static void get_coercion_expr(Node *fc_arg, deparse_context *fc_context,
				  Oid fc_resulttype, int32 fc_resulttypmod,
				  Node *fc_parentNode)
{
	StringInfo	fc_buf = fc_context->buf;

	/*
	 * 由于 parse_coerce.c 不会立即合并
	 * 应用到常量的长度强制函数，因此我们通常看到
	 * 这种情况是一个 Const，其 typmod 为 -1，且一个
	 * 长度强制函数在它上面。避免生成冗余输出。
	 * 但是，要小心在用户实际上写了类似
	 * 'foo'::text::char(3) 的内容时抑制转换。
	 *
	 * 注意：似乎我们没有考虑到需要为这样的 Const
	 * 打印 COLLATE 子句的可能性。然而，Const 只有在常量折叠后树中才能
	 * 具有非默认排序，而在该树中，长度强制也会被折叠。
	 * 参见 coerce_to_target_type() 中对 CollateExpr 的特殊处理：
	 * 任何排序标记都将在强制节点上方，而不是下方。
	 */
	if (fc_arg && IsA(fc_arg, Const) &&
		((Const *) fc_arg)->consttype == fc_resulttype &&
		((Const *) fc_arg)->consttypmod == -1)
	{
		/* 显示常量，不带普通的 ::typename 装饰 */
		get_const_expr((Const *) fc_arg, fc_context, -1);
	}
	else
	{
		if (!PRETTY_PAREN(fc_context))
			appendStringInfoChar(fc_buf, '(');
		get_rule_expr_paren(fc_arg, fc_context, false, fc_parentNode);
		if (!PRETTY_PAREN(fc_context))
			appendStringInfoChar(fc_buf, ')');
	}

	/*
	 * 永远不要发出 resulttype(arg) 函数符号。 
	 * pg_proc 条目可能会优先，pg_temp 中的 resulttype 
	 * 会要求模式限定，而 format_type_with_typemod() 通常会省略。
	 * 我们已经标准化为 arg::resulttype，但 CAST(arg AS resulttype) 
	 * 符号也可以正常工作。
	 */
	appendStringInfo(fc_buf, "::%s",
					 format_type_with_typemod(fc_resulttype, fc_resulttypmod));
}

/* ----------
 * get_const_expr
 *
 *	生成 Const 的字符串表示
 *
 * showtype 可以为 -1，以便永远不显示 "::typename" 装饰，或 +1 
 * 以便始终显示，或 0 只有在常量不会默认被认为是合适类型时才显示。
 *
 * 如果 Const 的排序与其类型的默认排序不同，也要显示它。
 * 当 showtype 为 -1 时，我们不能这样做（因为这意味着调用者将
 * 打印 "::typename"，而且我们不能在中间放置 COLLATE 子句）。
 * 在这种情况下，保证不遗漏排序是调用者的责任。
 * ----------
 */
static void get_const_expr(Const *fc_constval, deparse_context *fc_context, int fc_showtype)
{
	StringInfo	fc_buf = fc_context->buf;
	Oid			fc_typoutput;
	bool		fc_typIsVarlena;
	char	   *fc_extval;
	bool		fc_needlabel = false;

	if (fc_constval->constisnull)
	{
		/*
		 * 始终标记 NULL 常量的类型，以防在重解析时
		 * 出现类型误判。
		 */
		appendStringInfoString(fc_buf, "NULL");
		if (fc_showtype >= 0)
		{
			appendStringInfo(fc_buf, "::%s",
							 format_type_with_typemod(fc_constval->consttype,
													  fc_constval->consttypmod));
			get_const_collation(fc_constval, fc_context);
		}
		return;
	}

	getTypeOutputInfo(fc_constval->consttype,
					  &fc_typoutput, &fc_typIsVarlena);

	fc_extval = OidOutputFunctionCall(fc_typoutput, fc_constval->constvalue);

	switch (fc_constval->consttype)
	{
		case INT4OID:

			/*
			 * INT4可以不带任何修饰地打印，除非它是
			 * 负数；在这种情况下，将其打印为'-nnn'::integer以确保
			 * 输出被重新解析为一个常量，而不是常量
			 * 加运算符。在大多数情况下，我们可以打印
			 * (-nnn)而不出错误，因为gram.y处理负数
			 * 字面量的方式；但是对于INT_MIN来说，这并不奏效，而且
			 * 看起来也不那么美观。
			 */
			if (fc_extval[0] != '-')
				appendStringInfoString(fc_buf, fc_extval);
			else
			{
				appendStringInfo(fc_buf, "'%s'", fc_extval);
				fc_needlabel = true;	/* 我们必须附加一个类型转换 */
			}
			break;

		case NUMERICOID:

			/*
			 * NUMERIC可以不带引号地打印，如果它看起来像是一个浮点
			 * 常量（不是整数，且不是Infinity或NaN），并且没有
			 * 领头符号（与INT4相同的原因）。
			 */
			if (isdigit((unsigned char) fc_extval[0]) &&
				strcspn(fc_extval, "eE.") != strlen(fc_extval))
			{
				appendStringInfoString(fc_buf, fc_extval);
			}
			else
			{
				appendStringInfo(fc_buf, "'%s'", fc_extval);
				fc_needlabel = true;	/* 我们必须附加一个类型转换 */
			}
			break;

		case BOOLOID:
			if (strcmp(fc_extval, "t") == 0)
				appendStringInfoString(fc_buf, "true");
			else
				appendStringInfoString(fc_buf, "false");
			break;

		default:
			fc_simple_quote_literal(fc_buf, fc_extval);
			break;
	}

	pfree(fc_extval);

	if (fc_showtype < 0)
		return;

	/* 除非我们将其打印为未指定类型的字符串，否则标记 */
	switch (fc_constval->consttype)
	{
		case BOOLOID:
		case UNKNOWNOID:
			/* 这些类型可以保持无标签状态 */
			fc_needlabel = false;
			break;
		case INT4OID:
			/* 我们在上面已确定是否需要标签 */
			break;
		case NUMERICOID:

			/*
			 * 看起来像浮点数的常量将被标记为numeric，我们
			 * 在上面检查过；但是如果有非默认的typmod，我们需要
			 * 显示它。
			 */
			fc_needlabel |= (fc_constval->consttypmod >= 0);
			break;
		default:
			fc_needlabel = true;
			break;
	}
	if (fc_needlabel || fc_showtype > 0)
		appendStringInfo(fc_buf, "::%s",
						 format_type_with_typemod(fc_constval->consttype,
												  fc_constval->consttypmod));

	get_const_collation(fc_constval, fc_context);
}

/*
 * get_const_expr的辅助函数：必要时附加COLLATE
 */
static void get_const_collation(Const *fc_constval, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;

	if (OidIsValid(fc_constval->constcollid))
	{
		Oid			fc_typcollation = get_typcollation(fc_constval->consttype);

		if (fc_constval->constcollid != fc_typcollation)
		{
			appendStringInfo(fc_buf, " COLLATE %s",
							 generate_collation_name(fc_constval->constcollid));
		}
	}
}

/*
 * simple_quote_literal - 将字符串格式化为SQL字面量，附加到buf
 */
static void fc_simple_quote_literal(StringInfo fc_buf, const char *fc_val)
{
	const char *fc_valptr;

	/*
	 * 我们根据标准遵循字符串的当前设置来形成字符串
	 * 字面量；我们从不使用E''。用户有责任确保结果被正确使用。
	 */
	appendStringInfoChar(fc_buf, '\'');
	for (fc_valptr = fc_val; *fc_valptr; fc_valptr++)
	{
		char		fc_ch = *fc_valptr;

		if (SQL_STR_DOUBLE(fc_ch, !standard_conforming_strings))
			appendStringInfoChar(fc_buf, fc_ch);
		appendStringInfoChar(fc_buf, fc_ch);
	}
	appendStringInfoChar(fc_buf, '\'');
}


/* ----------
 * get_sublink_expr - 解析回一个子链接
 * ----------
 */
static void get_sublink_expr(SubLink *fc_sublink, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	Query	   *fc_query = (Query *) (fc_sublink->subselect);
	char	   *fc_opname = NULL;
	bool		fc_need_paren;

	if (fc_sublink->subLinkType == ARRAY_SUBLINK)
		appendStringInfoString(fc_buf, "ARRAY(");
	else
		appendStringInfoChar(fc_buf, '(');

	/*
	 * 请注意，当有多个组合运算符时，我们只打印第一个运算符的名称。
	 * 这是一个近似值，在各种场景中可能出错（不同模式中的运算符，
	 * 重命名的运算符等），但我们对此无能为力，因为
	 * 语法只允许显示一个运算符。
	 */
	if (fc_sublink->testexpr)
	{
		if (IsA(fc_sublink->testexpr, OpExpr))
		{
			/* 单个组合运算符 */
			OpExpr	   *fc_opexpr = (OpExpr *) fc_sublink->testexpr;

			get_rule_expr(linitial(fc_opexpr->args), fc_context, true);
			fc_opname = fc_generate_operator_name(fc_opexpr->opno,
											exprType(linitial(fc_opexpr->args)),
											exprType(lsecond(fc_opexpr->args)));
		}
		else if (IsA(fc_sublink->testexpr, BoolExpr))
		{
			/* 多个组合运算符，=或<>情况 */
			char	   *fc_sep;
			ListCell   *fc_l;

			appendStringInfoChar(fc_buf, '(');
			fc_sep = "";
			foreach(fc_l, ((BoolExpr *) fc_sublink->testexpr)->args)
			{
				OpExpr	   *fc_opexpr = lfirst_node(OpExpr, fc_l);

				appendStringInfoString(fc_buf, fc_sep);
				get_rule_expr(linitial(fc_opexpr->args), fc_context, true);
				if (!fc_opname)
					fc_opname = fc_generate_operator_name(fc_opexpr->opno,
													exprType(linitial(fc_opexpr->args)),
													exprType(lsecond(fc_opexpr->args)));
				fc_sep = ", ";
			}
			appendStringInfoChar(fc_buf, ')');
		}
		else if (IsA(fc_sublink->testexpr, RowCompareExpr))
		{
			/* 多个组合运算符，< <= > >=情况 */
			RowCompareExpr *fc_rcexpr = (RowCompareExpr *) fc_sublink->testexpr;

			appendStringInfoChar(fc_buf, '(');
			get_rule_expr((Node *) fc_rcexpr->largs, fc_context, true);
			fc_opname = fc_generate_operator_name(linitial_oid(fc_rcexpr->opnos),
											exprType(linitial(fc_rcexpr->largs)),
											exprType(linitial(fc_rcexpr->rargs)));
			appendStringInfoChar(fc_buf, ')');
		}
		else
			elog(ERROR, "unrecognized testexpr type: %d",
				 (int) nodeTag(fc_sublink->testexpr));
	}

	fc_need_paren = true;

	switch (fc_sublink->subLinkType)
	{
		case EXISTS_SUBLINK:
			appendStringInfoString(fc_buf, "EXISTS ");
			break;

		case ANY_SUBLINK:
			if (strcmp(fc_opname, "=") == 0)	/* 将= ANY表示为IN */
				appendStringInfoString(fc_buf, " IN ");
			else
				appendStringInfo(fc_buf, " %s ANY ", fc_opname);
			break;

		case ALL_SUBLINK:
			appendStringInfo(fc_buf, " %s ALL ", fc_opname);
			break;

		case ROWCOMPARE_SUBLINK:
			appendStringInfo(fc_buf, " %s ", fc_opname);
			break;

		case EXPR_SUBLINK:
		case MULTIEXPR_SUBLINK:
		case ARRAY_SUBLINK:
			fc_need_paren = false;
			break;

		case CTE_SUBLINK:		/* 不应在SubLink中出现 */
		default:
			elog(ERROR, "unrecognized sublink type: %d",
				 (int) fc_sublink->subLinkType);
			break;
	}

	if (fc_need_paren)
		appendStringInfoChar(fc_buf, '(');

	get_query_def(fc_query, fc_buf, fc_context->namespaces, NULL, false,
				  fc_context->prettyFlags, fc_context->wrapColumn,
				  fc_context->indentLevel);

	if (fc_need_paren)
		appendStringInfoString(fc_buf, "))");
	else
		appendStringInfoChar(fc_buf, ')');
}


/* ----------
 * get_tablefunc - 解析回一个表函数
 * ----------
 */
static void get_tablefunc(TableFunc *fc_tf, deparse_context *fc_context, bool fc_showimplicit)
{
	StringInfo	fc_buf = fc_context->buf;

	/* XMLTABLE是唯一存在的实现。 */

	appendStringInfoString(fc_buf, "XMLTABLE(");

	if (fc_tf->ns_uris != NIL)
	{
		ListCell   *fc_lc1,
				   *fc_lc2;
		bool		fc_first = true;

		appendStringInfoString(fc_buf, "XMLNAMESPACES (");
		forboth(fc_lc1, fc_tf->ns_uris, fc_lc2, fc_tf->ns_names)
		{
			Node	   *fc_expr = (Node *) lfirst(fc_lc1);
			String	   *fc_ns_node = lfirst_node(String, fc_lc2);

			if (!fc_first)
				appendStringInfoString(fc_buf, ", ");
			else
				fc_first = false;

			if (fc_ns_node != NULL)
			{
				get_rule_expr(fc_expr, fc_context, fc_showimplicit);
				appendStringInfo(fc_buf, " AS %s",
								 quote_identifier(strVal(fc_ns_node)));
			}
			else
			{
				appendStringInfoString(fc_buf, "DEFAULT ");
				get_rule_expr(fc_expr, fc_context, fc_showimplicit);
			}
		}
		appendStringInfoString(fc_buf, "), ");
	}

	appendStringInfoChar(fc_buf, '(');
	get_rule_expr((Node *) fc_tf->rowexpr, fc_context, fc_showimplicit);
	appendStringInfoString(fc_buf, ") PASSING (");
	get_rule_expr((Node *) fc_tf->docexpr, fc_context, fc_showimplicit);
	appendStringInfoChar(fc_buf, ')');

	if (fc_tf->colexprs != NIL)
	{
		ListCell   *fc_l1;
		ListCell   *fc_l2;
		ListCell   *fc_l3;
		ListCell   *fc_l4;
		ListCell   *fc_l5;
		int			fc_colnum = 0;

		appendStringInfoString(fc_buf, " COLUMNS ");
		forfive(fc_l1, fc_tf->colnames, fc_l2, fc_tf->coltypes, fc_l3, fc_tf->coltypmods,
				fc_l4, fc_tf->colexprs, fc_l5, fc_tf->coldefexprs)
		{
			char	   *fc_colname = strVal(lfirst(fc_l1));
			Oid			fc_typid = lfirst_oid(fc_l2);
			int32		fc_typmod = lfirst_int(fc_l3);
			Node	   *fc_colexpr = (Node *) lfirst(fc_l4);
			Node	   *fc_coldefexpr = (Node *) lfirst(fc_l5);
			bool		fc_ordinality = (fc_tf->ordinalitycol == fc_colnum);
			bool		fc_notnull = bms_is_member(fc_colnum, fc_tf->notnulls);

			if (fc_colnum > 0)
				appendStringInfoString(fc_buf, ", ");
			fc_colnum++;

			appendStringInfo(fc_buf, "%s %s", quote_identifier(fc_colname),
							 fc_ordinality ? "FOR ORDINALITY" :
							 format_type_with_typemod(fc_typid, fc_typmod));
			if (fc_ordinality)
				continue;

			if (fc_coldefexpr != NULL)
			{
				appendStringInfoString(fc_buf, " DEFAULT (");
				get_rule_expr((Node *) fc_coldefexpr, fc_context, fc_showimplicit);
				appendStringInfoChar(fc_buf, ')');
			}
			if (fc_colexpr != NULL)
			{
				appendStringInfoString(fc_buf, " PATH (");
				get_rule_expr((Node *) fc_colexpr, fc_context, fc_showimplicit);
				appendStringInfoChar(fc_buf, ')');
			}
			if (fc_notnull)
				appendStringInfoString(fc_buf, " NOT NULL");
		}
	}

	appendStringInfoChar(fc_buf, ')');
}

/* ----------
 * get_from_clause - 解析回一个FROM子句
 *
 * "prefix"是指示FROM元素列表开始的关键字。用于解析BACK
 * SELECT和UPDATE时是FROM，而用于解析回DELETE时是USING。
 * ----------
 */
static void get_from_clause(Query *fc_query, const char *fc_prefix, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	bool		fc_first = true;
	ListCell   *fc_l;

	/*
	 * 我们使用查询的jointree作为打印的指南。然而，我们
	 * 必须忽略标记为不在FromCl的自动添加RTE。（这些只能
	 * 出现在jointree的顶层，因此这里检查就足够了。）此检查
	 * 还确保我们忽略NEW和OLD的规则伪RTE。
	 */
	foreach(fc_l, fc_query->jointree->fromlist)
	{
		Node	   *fc_jtnode = (Node *) lfirst(fc_l);

		if (IsA(fc_jtnode, RangeTblRef))
		{
			int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;
			RangeTblEntry *fc_rte = rt_fetch(fc_varno, fc_query->rtable);

			if (!fc_rte->inFromCl)
				continue;
		}

		if (fc_first)
		{
			fc_appendContextKeyword(fc_context, fc_prefix,
								 -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
			fc_first = false;

			get_from_clause_item(fc_jtnode, fc_query, fc_context);
		}
		else
		{
			StringInfoData fc_itembuf;

			appendStringInfoString(fc_buf, ", ");

			/*
			 * 将新FROM项的文本放入itembuf中，以便在获取后
			 * 决定是否需要换行。
			 */
			initStringInfo(&fc_itembuf);
			fc_context->buf = &fc_itembuf;

			get_from_clause_item(fc_jtnode, fc_query, fc_context);

			/* 恢复上下文的输出缓冲区 */
			fc_context->buf = fc_buf;

			/* 如果启用则考虑换行 */
			if (PRETTY_INDENT(fc_context) && fc_context->wrapColumn >= 0)
			{
				/* 新项是否以新行开始？ */
				if (fc_itembuf.len > 0 && fc_itembuf.data[0] == '\n')
				{
					/* 如果是，我们就不应该添加任何内容 */
					/* 相反，删除当前 buf 中的任何尾随空格 */
					fc_removeStringInfoSpaces(fc_buf);
				}
				else
				{
					char	   *fc_trailing_nl;

					/* 在缓冲区中找到当前行的起始位置 */
					fc_trailing_nl = strrchr(fc_buf->data, '\n');
					if (fc_trailing_nl == NULL)
						fc_trailing_nl = fc_buf->data;
					else
						fc_trailing_nl++;

					/*
					 * 如果新项将导致溢出，则添加换行符和一些缩进。
					 */
					if (strlen(fc_trailing_nl) + fc_itembuf.len > fc_context->wrapColumn)
						fc_appendContextKeyword(fc_context, "", -PRETTYINDENT_STD,
											 PRETTYINDENT_STD,
											 PRETTYINDENT_VAR);
				}
			}

			/* 添加新项 */
			appendBinaryStringInfo(fc_buf, fc_itembuf.data, fc_itembuf.len);

			/* 清理 */
			pfree(fc_itembuf.data);
		}
	}
}

static void get_from_clause_item(Node *fc_jtnode, Query *fc_query, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	deparse_namespace *fc_dpns = (deparse_namespace *) linitial(fc_context->namespaces);

	if (IsA(fc_jtnode, RangeTblRef))
	{
		int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;
		RangeTblEntry *fc_rte = rt_fetch(fc_varno, fc_query->rtable);
		deparse_columns *fc_colinfo = deparse_columns_fetch(fc_varno, fc_dpns);
		RangeTblFunction *fc_rtfunc1 = NULL;

		if (fc_rte->lateral)
			appendStringInfoString(fc_buf, "LATERAL ");

		/* 打印FROM项本身 */
		switch (fc_rte->rtekind)
		{
			case RTE_RELATION:
				/* 普通关系RTE */
				appendStringInfo(fc_buf, "%s%s",
								 only_marker(fc_rte),
								 fc_generate_relation_name(fc_rte->relid,
														fc_context->namespaces));
				break;
			case RTE_SUBQUERY:
				/* 子查询 RTE */
				appendStringInfoChar(fc_buf, '(');
				get_query_def(fc_rte->subquery, fc_buf, fc_context->namespaces, NULL,
							  true,
							  fc_context->prettyFlags, fc_context->wrapColumn,
							  fc_context->indentLevel);
				appendStringInfoChar(fc_buf, ')');
				break;
			case RTE_FUNCTION:
				/* 函数 RTE */
				fc_rtfunc1 = (RangeTblFunction *) linitial(fc_rte->functions);

				/*
				 * 对于只有一个函数，除非它有coldeflist和WITH ORDINALITY，
				 * 否则省略ROWS FROM()语法。如果两个都有，我们
				 * 必须使用ROWS FROM()语法，以避免关于
				 * coldeflist是否包括序列列的模糊性。
				 */
				if (list_length(fc_rte->functions) == 1 &&
					(fc_rtfunc1->funccolnames == NIL || !fc_rte->funcordinality))
				{
					get_rule_expr_funccall(fc_rtfunc1->funcexpr, fc_context, true);
					/* 如果有coldeflist，我们将在下面打印它 */
				}
				else
				{
					bool		fc_all_unnest;
					ListCell   *fc_lc;

					/*
					 * 如果列表中的所有函数调用都是unnest，
					 * 并且没有需要coldeflist的，则将列表折叠为
					 * UNNEST(args)。（如果我们有多个内置的
					 * unnest函数，这会变得更加困难。）
					 *
					 * XXX 这相当丑陋，因为它对解析器将如何处理
					 * 输出做了不太未来-proof的假设；但替代的
					 * 是发出我们的非标准ROWS FROM()表示法，可能
					 * 被视为完全符合规范的多参数UNNEST()。
					 */
					fc_all_unnest = true;
					foreach(fc_lc, fc_rte->functions)
					{
						RangeTblFunction *fc_rtfunc = (RangeTblFunction *) lfirst(fc_lc);

						if (!IsA(fc_rtfunc->funcexpr, FuncExpr) ||
							((FuncExpr *) fc_rtfunc->funcexpr)->funcid != F_UNNEST_ANYARRAY ||
							fc_rtfunc->funccolnames != NIL)
						{
							fc_all_unnest = false;
							break;
						}
					}

					if (fc_all_unnest)
					{
						List	   *fc_allargs = NIL;

						foreach(fc_lc, fc_rte->functions)
						{
							RangeTblFunction *fc_rtfunc = (RangeTblFunction *) lfirst(fc_lc);
							List	   *fc_args = ((FuncExpr *) fc_rtfunc->funcexpr)->args;

							fc_allargs = list_concat(fc_allargs, fc_args);
						}

						appendStringInfoString(fc_buf, "UNNEST(");
						get_rule_expr((Node *) fc_allargs, fc_context, true);
						appendStringInfoChar(fc_buf, ')');
					}
					else
					{
						int			fc_funcno = 0;

						appendStringInfoString(fc_buf, "ROWS FROM(");
						foreach(fc_lc, fc_rte->functions)
						{
							RangeTblFunction *fc_rtfunc = (RangeTblFunction *) lfirst(fc_lc);

							if (fc_funcno > 0)
								appendStringInfoString(fc_buf, ", ");
							get_rule_expr_funccall(fc_rtfunc->funcexpr, fc_context, true);
							if (fc_rtfunc->funccolnames != NIL)
							{
								/* 重新构建列定义列表 */
								appendStringInfoString(fc_buf, " AS ");
								get_from_clause_coldeflist(fc_rtfunc,
														   NULL,
														   fc_context);
							}
							fc_funcno++;
						}
						appendStringInfoChar(fc_buf, ')');
					}
					/* 防止下面打印重复的 coldeflist */
					fc_rtfunc1 = NULL;
				}
				if (fc_rte->funcordinality)
					appendStringInfoString(fc_buf, " WITH ORDINALITY");
				break;
			case RTE_TABLEFUNC:
				get_tablefunc(fc_rte->tablefunc, fc_context, true);
				break;
			case RTE_VALUES:
				/* 值列表 RTE */
				appendStringInfoChar(fc_buf, '(');
				get_values_def(fc_rte->values_lists, fc_context);
				appendStringInfoChar(fc_buf, ')');
				break;
			case RTE_CTE:
				appendStringInfoString(fc_buf, quote_identifier(fc_rte->ctename));
				break;
			default:
				elog(ERROR, "unrecognized RTE kind: %d", (int) fc_rte->rtekind);
				break;
		}

		/* 如果需要，打印关系别名 */
		get_rte_alias(fc_rte, fc_varno, false, fc_context);

		/* 如果需要，打印列定义或别名 */
		if (fc_rtfunc1 && fc_rtfunc1->funccolnames != NIL)
		{
			/* 重构列定义列表，这也是别名 */
			get_from_clause_coldeflist(fc_rtfunc1, fc_colinfo, fc_context);
		}
		else
		{
			/* 否则根据需要打印列别名 */
			get_column_alias_list(fc_colinfo, fc_context);
		}

		/* Tablesample 子句必须在任何别名之后 */
		if (fc_rte->rtekind == RTE_RELATION && fc_rte->tablesample)
			get_tablesample_def(fc_rte->tablesample, fc_context);
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;
		deparse_columns *fc_colinfo = deparse_columns_fetch(fc_j->rtindex, fc_dpns);
		bool		fc_need_paren_on_right;

		fc_need_paren_on_right = PRETTY_PAREN(fc_context) &&
			!IsA(fc_j->rarg, RangeTblRef) &&
			!(IsA(fc_j->rarg, JoinExpr) && ((JoinExpr *) fc_j->rarg)->alias != NULL);

		if (!PRETTY_PAREN(fc_context) || fc_j->alias != NULL)
			appendStringInfoChar(fc_buf, '(');

		get_from_clause_item(fc_j->larg, fc_query, fc_context);

		switch (fc_j->jointype)
		{
			case JOIN_INNER:
				if (fc_j->quals)
					fc_appendContextKeyword(fc_context, " JOIN ",
										 -PRETTYINDENT_STD,
										 PRETTYINDENT_STD,
										 PRETTYINDENT_JOIN);
				else
					fc_appendContextKeyword(fc_context, " CROSS JOIN ",
										 -PRETTYINDENT_STD,
										 PRETTYINDENT_STD,
										 PRETTYINDENT_JOIN);
				break;
			case JOIN_LEFT:
				fc_appendContextKeyword(fc_context, " LEFT JOIN ",
									 -PRETTYINDENT_STD,
									 PRETTYINDENT_STD,
									 PRETTYINDENT_JOIN);
				break;
			case JOIN_FULL:
				fc_appendContextKeyword(fc_context, " FULL JOIN ",
									 -PRETTYINDENT_STD,
									 PRETTYINDENT_STD,
									 PRETTYINDENT_JOIN);
				break;
			case JOIN_RIGHT:
				fc_appendContextKeyword(fc_context, " RIGHT JOIN ",
									 -PRETTYINDENT_STD,
									 PRETTYINDENT_STD,
									 PRETTYINDENT_JOIN);
				break;
			default:
				elog(ERROR, "unrecognized join type: %d",
					 (int) fc_j->jointype);
		}

		if (fc_need_paren_on_right)
			appendStringInfoChar(fc_buf, '(');
		get_from_clause_item(fc_j->rarg, fc_query, fc_context);
		if (fc_need_paren_on_right)
			appendStringInfoChar(fc_buf, ')');

		if (fc_j->usingClause)
		{
			ListCell   *fc_lc;
			bool		fc_first = true;

			appendStringInfoString(fc_buf, " USING (");
			/* 使用分配的名称，而不是 usingClause 中的内容 */
			foreach(fc_lc, fc_colinfo->usingNames)
			{
				char	   *fc_colname = (char *) lfirst(fc_lc);

				if (fc_first)
					fc_first = false;
				else
					appendStringInfoString(fc_buf, ", ");
				appendStringInfoString(fc_buf, quote_identifier(fc_colname));
			}
			appendStringInfoChar(fc_buf, ')');

			if (fc_j->join_using_alias)
				appendStringInfo(fc_buf, " AS %s",
								 quote_identifier(fc_j->join_using_alias->aliasname));
		}
		else if (fc_j->quals)
		{
			appendStringInfoString(fc_buf, " ON ");
			if (!PRETTY_PAREN(fc_context))
				appendStringInfoChar(fc_buf, '(');
			get_rule_expr(fc_j->quals, fc_context, false);
			if (!PRETTY_PAREN(fc_context))
				appendStringInfoChar(fc_buf, ')');
		}
		else if (fc_j->jointype != JOIN_INNER)
		{
			/* 如果我们上面没有说 CROSS JOIN，我们必须提供一个 ON */
			appendStringInfoString(fc_buf, " ON TRUE");
		}

		if (!PRETTY_PAREN(fc_context) || fc_j->alias != NULL)
			appendStringInfoChar(fc_buf, ')');

		/* 是的，在右括号后放别名是正确的... */
		if (fc_j->alias != NULL)
		{
			/*
			 * 注意，只有在原本存在别名的情况下，才正确生成别名子句。
			 * 否则我们将把命名连接转换为未命名连接，反之亦然，这会产生我们不想要的
			 * 语义微妙性。然而，我们可能打印出与原始不同的别名。
			 */
			appendStringInfo(fc_buf, " %s",
							 quote_identifier(get_rtable_name(fc_j->rtindex,
															  fc_context)));
			get_column_alias_list(fc_colinfo, fc_context);
		}
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
}

/*
 * get_rte_alias - 如果需要，打印关系的别名
 *
 * 如果打印，别名前会有一个空格，或者如果 use_as 为真，则为 " AS "。
 */
static void get_rte_alias(RangeTblEntry *fc_rte, int fc_varno, bool fc_use_as,
			  deparse_context *fc_context)
{
	deparse_namespace *fc_dpns = (deparse_namespace *) linitial(fc_context->namespaces);
	char	   *fc_refname = get_rtable_name(fc_varno, fc_context);
	deparse_columns *fc_colinfo = deparse_columns_fetch(fc_varno, fc_dpns);
	bool		fc_printalias = false;

	if (fc_rte->alias != NULL)
	{
		/* 如果用户提供了别名，总是打印别名 */
		fc_printalias = true;
	}
	else if (fc_colinfo->printaliases)
	{
		/* 如果需要打印列别名，总是打印别名 */
		fc_printalias = true;
	}
	else if (fc_rte->rtekind == RTE_RELATION)
	{
		/*
		 * 如果别名与关系名称相同，则无需打印别名（这通常是
		 * 情况，但如果 set_rtable_names 必须解决冲突，则不是）。
		 */
		if (strcmp(fc_refname, get_relation_name(fc_rte->relid)) != 0)
			fc_printalias = true;
	}
	else if (fc_rte->rtekind == RTE_FUNCTION)
	{
		/*
		 * 对于函数 RTE，总是打印别名。这涵盖了对
		 * 函数的可能重命名和/或
		 * 对于不简单函数的 FigureColname 规则的不稳定性。
		 * 注意，对于 columndef 列表的情况，我们无论如何都需要强制执行。
		 */
		fc_printalias = true;
	}
	else if (fc_rte->rtekind == RTE_SUBQUERY ||
			 fc_rte->rtekind == RTE_VALUES)
	{
		/* SUBQUERY 和 VALUES 的别名在语法上是必需的 */
		fc_printalias = true;
	}
	else if (fc_rte->rtekind == RTE_CTE)
	{
		/*
		 * 如果别名与 CTE 名称相同，则无需打印别名（这通常是
		 * 情况，但如果 set_rtable_names 必须解决冲突，则不是）。
		 */
		if (strcmp(fc_refname, fc_rte->ctename) != 0)
			fc_printalias = true;
	}

	if (fc_printalias)
		appendStringInfo(fc_context->buf, "%s%s",
						 fc_use_as ? " AS " : " ",
						 quote_identifier(fc_refname));
}

/*
 * get_column_alias_list - 打印 RTE 的列别名列表
 *
 * 调用者必须已经打印出关系的别名。
 */
static void get_column_alias_list(deparse_columns *fc_colinfo, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	int			fc_i;
	bool		fc_first = true;

	/* 如果不需要，则不要打印别名 */
	if (!fc_colinfo->printaliases)
		return;

	for (fc_i = 0; fc_i < fc_colinfo->num_new_cols; fc_i++)
	{
		char	   *fc_colname = fc_colinfo->new_colnames[fc_i];

		if (fc_first)
		{
			appendStringInfoChar(fc_buf, '(');
			fc_first = false;
		}
		else
			appendStringInfoString(fc_buf, ", ");
		appendStringInfoString(fc_buf, quote_identifier(fc_colname));
	}
	if (!fc_first)
		appendStringInfoChar(fc_buf, ')');
}

/*
 * get_from_clause_coldeflist - 复制 FROM 子句 coldeflist
 *
 * 在打印顶层 coldeflist（在语法上也是
 * 关系的列别名列表）时，使用来自 colinfo 的列名。
 * 但是在打印嵌入在 ROWS FROM() 内部的 coldeflist 时，
 * 我们更喜欢使用原始 coldeflist 的名称，这些名称可在 rtfunc->funccolnames 中获得。
 * 传递 NULL 给 colinfo 以选择后者的行为。
 *
 * coldeflist 立即（没有空格）附加到 buf。调用者负责
 * 确保在其前面存在别名或 AS。
 */
static void get_from_clause_coldeflist(RangeTblFunction *fc_rtfunc,
						   deparse_columns *fc_colinfo,
						   deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	ListCell   *fc_l1;
	ListCell   *fc_l2;
	ListCell   *fc_l3;
	ListCell   *fc_l4;
	int			fc_i;

	appendStringInfoChar(fc_buf, '(');

	fc_i = 0;
	forfour(fc_l1, fc_rtfunc->funccoltypes,
			fc_l2, fc_rtfunc->funccoltypmods,
			fc_l3, fc_rtfunc->funccolcollations,
			fc_l4, fc_rtfunc->funccolnames)
	{
		Oid			fc_atttypid = lfirst_oid(fc_l1);
		int32		fc_atttypmod = lfirst_int(fc_l2);
		Oid			fc_attcollation = lfirst_oid(fc_l3);
		char	   *fc_attname;

		if (fc_colinfo)
			fc_attname = fc_colinfo->colnames[fc_i];
		else
			fc_attname = strVal(lfirst(fc_l4));

		Assert(fc_attname);		/* 这里不应该有任何丢失的列 */

		if (fc_i > 0)
			appendStringInfoString(fc_buf, ", ");
		appendStringInfo(fc_buf, "%s %s",
						 quote_identifier(fc_attname),
						 format_type_with_typemod(fc_atttypid, fc_atttypmod));
		if (OidIsValid(fc_attcollation) &&
			fc_attcollation != get_typcollation(fc_atttypid))
			appendStringInfo(fc_buf, " COLLATE %s",
							 generate_collation_name(fc_attcollation));

		fc_i++;
	}

	appendStringInfoChar(fc_buf, ')');
}

/*
 * get_tablesample_def - 打印 TableSampleClause
 */
static void get_tablesample_def(TableSampleClause *fc_tablesample, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	Oid			fc_argtypes[1];
	int			fc_nargs;
	ListCell   *fc_l;

	/*
	 * 如果处理程序的函数名称无法通过当前搜索路径中的查找解析，
	 * 我们应该给该函数名称加上限定符。
	 */
	fc_argtypes[0] = INTERNALOID;
	appendStringInfo(fc_buf, " TABLESAMPLE %s (",
					 fc_generate_function_name(fc_tablesample->tsmhandler, 1,
											NIL, fc_argtypes,
											false, NULL, EXPR_KIND_NONE));

	fc_nargs = 0;
	foreach(fc_l, fc_tablesample->args)
	{
		if (fc_nargs++ > 0)
			appendStringInfoString(fc_buf, ", ");
		get_rule_expr((Node *) lfirst(fc_l), fc_context, false);
	}
	appendStringInfoChar(fc_buf, ')');

	if (fc_tablesample->repeatable != NULL)
	{
		appendStringInfoString(fc_buf, " REPEATABLE (");
		get_rule_expr((Node *) fc_tablesample->repeatable, fc_context, false);
		appendStringInfoChar(fc_buf, ')');
	}
}

/*
 * get_opclass_name - 获取索引操作符类的名称
 *
 * opclass 名称在 buf 中（后跟空格）附加。
 *
 * 如果 opclass 是给定实际数据类型的默认值，则抑制输出。
 * （如果不想要这种行为，只需传递 InvalidOid 作为 actual_datatype。）
 */
static void get_opclass_name(Oid fc_opclass, Oid fc_actual_datatype,
				 StringInfo fc_buf)
{
	HeapTuple	fc_ht_opc;
	Form_pg_opclass fc_opcrec;
	char	   *fc_opcname;
	char	   *fc_nspname;

	fc_ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(fc_opclass));
	if (!HeapTupleIsValid(fc_ht_opc))
		elog(ERROR, "cache lookup failed for opclass %u", fc_opclass);
	fc_opcrec = (Form_pg_opclass) GETSTRUCT(fc_ht_opc);

	if (!OidIsValid(fc_actual_datatype) ||
		GetDefaultOpClass(fc_actual_datatype, fc_opcrec->opcmethod) != fc_opclass)
	{
		/* 好的，我们需要 opclass 名称。我们需要给它加上修饰符吗？ */
		fc_opcname = NameStr(fc_opcrec->opcname);
		if (OpclassIsVisible(fc_opclass))
			appendStringInfo(fc_buf, " %s", quote_identifier(fc_opcname));
		else
		{
			fc_nspname = get_namespace_name_or_temp(fc_opcrec->opcnamespace);
			appendStringInfo(fc_buf, " %s.%s",
							 quote_identifier(fc_nspname),
							 quote_identifier(fc_opcname));
		}
	}
	ReleaseSysCache(fc_ht_opc);
}

/*
 * generate_opclass_name
 *		计算要显示的 opclass 名称，指定 OID
 *
 * 结果包括所有必要的引号和模式前缀。
 */
char * generate_opclass_name(Oid fc_opclass)
{
	StringInfoData fc_buf;

	initStringInfo(&fc_buf);
	get_opclass_name(fc_opclass, InvalidOid, &fc_buf);

	return &fc_buf.data[1];		/* get_opclass_name() 前面加空格 */
}

/*
 * processIndirection - 处理数组和子字段赋值
 *
 * 我们删除输入中出现的任何顶层 FieldStore 或赋值 SubscriptingRef 节点，
 * 将它们作为基列名的装饰打印（我们假设调用者刚刚打印了它）。
 * 我们可能还需要删除 CoerceToDomain 节点，但仅限于那些出现在赋值
 * 节点上方的节点。
 *
 * 返回要赋值的子表达式。
 */
static Node * fc_processIndirection(Node *fc_node, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	CoerceToDomain *fc_cdomain = NULL;

	for (;;)
	{
		if (fc_node == NULL)
			break;
		if (IsA(fc_node, FieldStore))
		{
			FieldStore *fc_fstore = (FieldStore *) fc_node;
			Oid			fc_typrelid;
			char	   *fc_fieldname;

			/* 查找元组类型 */
			fc_typrelid = get_typ_typrelid(fc_fstore->resulttype);
			if (!OidIsValid(fc_typrelid))
				elog(ERROR, "argument type %s of FieldStore is not a tuple type",
					 format_type_be(fc_fstore->resulttype));

			/*
			 * 打印字段名称。在存储规则中应该只有一个目标字段。
			 * 可执行目标列表中可能会有多个，但此函数不能用于该情况。
			 */
			Assert(list_length(fc_fstore->fieldnums) == 1);
			fc_fieldname = get_attname(fc_typrelid,
									linitial_int(fc_fstore->fieldnums), false);
			appendStringInfo(fc_buf, ".%s", quote_identifier(fc_fieldname));

			/*
			 * 我们忽略 arg，因为它应该是一个无趣的引用，指向
			 * 目标列或子列。
			 */
			fc_node = (Node *) linitial(fc_fstore->newvals);
		}
		else if (IsA(fc_node, SubscriptingRef))
		{
			SubscriptingRef *fc_sbsref = (SubscriptingRef *) fc_node;

			if (fc_sbsref->refassgnexpr == NULL)
				break;

			fc_printSubscripts(fc_sbsref, fc_context);

			/*
			 * 我们忽略 refexpr，因为它应该是一个无趣的引用
			 * 到目标列或子列。
			 */
			fc_node = (Node *) fc_sbsref->refassgnexpr;
		}
		else if (IsA(fc_node, CoerceToDomain))
		{
			fc_cdomain = (CoerceToDomain *) fc_node;
			/* 如果这是一个显式的领域强制转换，我们就完成了 */
			if (fc_cdomain->coercionformat != COERCE_IMPLICIT_CAST)
				break;
			/* 暂时越过 CoerceToDomain */
			fc_node = (Node *) fc_cdomain->arg;
		}
		else
			break;
	}

	/*
	 * 如果我们越过了一个 CoerceToDomain，其参数结果不是
	 * FieldStore 或数组赋值，则回退到 CoerceToDomain。
	 * （如果存在嵌套的隐式 CoerceToDomains，这样做并不足以完全正确，
	 * 但这种情况不应该发生。）
	 */
	if (fc_cdomain && fc_node == (Node *) fc_cdomain->arg)
		fc_node = (Node *) fc_cdomain;

	return fc_node;
}

static void fc_printSubscripts(SubscriptingRef *fc_sbsref, deparse_context *fc_context)
{
	StringInfo	fc_buf = fc_context->buf;
	ListCell   *fc_lowlist_item;
	ListCell   *fc_uplist_item;

	fc_lowlist_item = list_head(fc_sbsref->reflowerindexpr);	/*
 * 反解析函数调用。
 */
	foreach(fc_uplist_item, fc_sbsref->refupperindexpr)
	{
		appendStringInfoChar(fc_buf, '[');
		if (fc_lowlist_item)
		{
			/* 如果子表达式为 NULL，get_rule_expr 不会打印任何内容 */
			get_rule_expr((Node *) lfirst(fc_lowlist_item), fc_context, false);
			appendStringInfoChar(fc_buf, ':');
			fc_lowlist_item = lnext(fc_sbsref->reflowerindexpr, fc_lowlist_item);
		}
		/* 如果子表达式为 NULL，get_rule_expr 不会打印任何内容 */
		get_rule_expr((Node *) lfirst(fc_uplist_item), fc_context, false);
		appendStringInfoChar(fc_buf, ']');
	}
}

/*
 * quote_identifier			- 仅在需要时对标识符进行引用
 *
 * 当需要引用时，我们将分配所需的空间；稍微
 * 浪费空间，但为了符号简洁性是值得的。
 */
const char * quote_identifier(const char *fc_ident)
{
	/*
	 * 如果 ident 以小写字母或下划线开头，并且仅包含小写字母、
	 * 数字和下划线，并且不是任何 SQL 关键字，则可以避免引用。
	 * 否则，提供双引号。
	 */
	int			fc_nquotes = 0;
	bool		fc_safe;
	const char *fc_ptr;
	char	   *fc_result;
	char	   *fc_optr;

	/*
	 * 希望在这里使用 <ctype.h> 宏，但它们可能会产生不想要的
	 * 特定于区域的结果...
	 */
	fc_safe = ((fc_ident[0] >= 'a' && fc_ident[0] <= 'z') || fc_ident[0] == '_');

	for (fc_ptr = fc_ident; *fc_ptr; fc_ptr++)
	{
		char		fc_ch = *fc_ptr;

		if ((fc_ch >= 'a' && fc_ch <= 'z') ||
			(fc_ch >= '0' && fc_ch <= '9') ||
			(fc_ch == '_'))
		{
			/* 可以 */
		}
		else
		{
			fc_safe = false;
			if (fc_ch == '"')
				fc_nquotes++;
		}
	}

	if (quote_all_identifiers)
		fc_safe = false;

	if (fc_safe)
	{
		/*
		 * 检查是否为关键字。我们引用关键字，除了未保留的那些。
		 * （在某些情况下，我们可以避免引用 col_name 或 type_func_name
		 * 关键字，但判断这点似乎要复杂得多。）
		 *
		 * 注意：ScanKeywordLookup() 进行不区分大小写的比较，但
		 * 这没问题，因为我们已经知道这里都是小写。
		 */
		int			fc_kwnum = ScanKeywordLookup(fc_ident, &ScanKeywords);

		if (fc_kwnum >= 0 && ScanKeywordCategories[fc_kwnum] != UNRESERVED_KEYWORD)
			fc_safe = false;
	}

	if (fc_safe)
		return fc_ident;			/* 不需要更改 */

	fc_result = (char *) palloc(strlen(fc_ident) + fc_nquotes + 2 + 1);

	fc_optr = fc_result;
	*fc_optr++ = '"';
	for (fc_ptr = fc_ident; *fc_ptr; fc_ptr++)
	{
		char		fc_ch = *fc_ptr;

		if (fc_ch == '"')
			*fc_optr++ = '"';
		*fc_optr++ = fc_ch;
	}
	*fc_optr++ = '"';
	*fc_optr = '\0';

	return fc_result;
}

/*
 * quote_qualified_identifier	- 引用可能合格的标识符
 *
 * 返回形式为 qualifier.ident 的名称，或者如果 qualifier
 * 为 NULL，则返回 ident，必要时对每个组件进行引用。结果已分配。
 */
char * quote_qualified_identifier(const char *fc_qualifier,
						   const char *fc_ident)
{
	StringInfoData fc_buf;

	initStringInfo(&fc_buf);
	if (fc_qualifier)
		appendStringInfo(&fc_buf, "%s.", quote_identifier(fc_qualifier));
	appendStringInfoString(&fc_buf, quote_identifier(fc_ident));
	return fc_buf.data;
}

/*
 * get_relation_name
 *		获取由 OID 指定的关系的非限定名称
 *
 * 这与底层的 get_rel_name() 函数不同，因为如果 OID 有误，
 * 它将抛出错误，而不是默默返回 NULL。
 */
static char * get_relation_name(Oid fc_relid)
{
	char	   *fc_relname = get_rel_name(fc_relid);

	if (!fc_relname)
		elog(ERROR, "cache lookup failed for relation %u", fc_relid);
	return fc_relname;
}

/*
 * generate_relation_name
 *		计算要显示的名称，关系由 OID 指定
 *
 * 结果包含所有必要的引用和模式前缀。
 *
 * 如果 namespaces 不是 NIL，则它必须是一个 deparse_namespace 节点的列表。
 * 如果关系名称等于命名空间列表中可见的任何 CTE 名称，
 * 我们将强制限定关系名称。
 */
static char * fc_generate_relation_name(Oid fc_relid, List *fc_namespaces)
{
	HeapTuple	fc_tp;
	Form_pg_class fc_reltup;
	bool		fc_need_qual;
	ListCell   *fc_nslist;
	char	   *fc_relname;
	char	   *fc_nspname;
	char	   *fc_result;

	fc_tp = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tp))
		elog(ERROR, "cache lookup failed for relation %u", fc_relid);
	fc_reltup = (Form_pg_class) GETSTRUCT(fc_tp);
	fc_relname = NameStr(fc_reltup->relname);

	/* 检查冲突的 CTE 名称 */
	fc_need_qual = false;
	foreach(fc_nslist, fc_namespaces)
	{
		deparse_namespace *fc_dpns = (deparse_namespace *) lfirst(fc_nslist);
		ListCell   *fc_ctlist;

		foreach(fc_ctlist, fc_dpns->ctes)
		{
			CommonTableExpr *fc_cte = (CommonTableExpr *) lfirst(fc_ctlist);

			if (strcmp(fc_cte->ctename, fc_relname) == 0)
			{
				fc_need_qual = true;
				break;
			}
		}
		if (fc_need_qual)
			break;
	}

	/* 否则，如果在搜索路径中不可见，则限定名称 */
	if (!fc_need_qual)
		fc_need_qual = !RelationIsVisible(fc_relid);

	if (fc_need_qual)
		fc_nspname = get_namespace_name_or_temp(fc_reltup->relnamespace);
	else
		fc_nspname = NULL;

	fc_result = quote_qualified_identifier(fc_nspname, fc_relname);

	ReleaseSysCache(fc_tp);

	return fc_result;
}

/*
 * generate_qualified_relation_name
 *		计算要显示的关系名称，由 OID 指定
 *
 * 如上所述，但无条件地对名称进行模式限定。
 */
static char * fc_generate_qualified_relation_name(Oid fc_relid)
{
	HeapTuple	fc_tp;
	Form_pg_class fc_reltup;
	char	   *fc_relname;
	char	   *fc_nspname;
	char	   *fc_result;

	fc_tp = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tp))
		elog(ERROR, "cache lookup failed for relation %u", fc_relid);
	fc_reltup = (Form_pg_class) GETSTRUCT(fc_tp);
	fc_relname = NameStr(fc_reltup->relname);

	fc_nspname = get_namespace_name_or_temp(fc_reltup->relnamespace);
	if (!fc_nspname)
		elog(ERROR, "cache lookup failed for namespace %u",
			 fc_reltup->relnamespace);

	fc_result = quote_qualified_identifier(fc_nspname, fc_relname);

	ReleaseSysCache(fc_tp);

	return fc_result;
}

/*
 * generate_function_name
 *		计算要显示的函数名称，由 OID 指定，
 *		假设它使用指定的实际参数名称和类型被调用。
 *		（因为模糊函数解析规则，参数类型很重要。）
 *
 * 如果我们处理的是一个潜在的可变参数函数（实际上，
 * 这意味着 FuncExpr 或 Aggref，而不是调用函数的其他方式），那么
 * has_variadic 必须指定可变参数是否已合并，
 * 并且 *use_variadic_p 将设置为指示是否在
 * 输出中打印 VARIADIC。对于非 FuncExpr 的情况，has_variadic 应该为假，
 * use_variadic_p 可以为 NULL。
 *
 * 结果包含所有必要的引用和模式前缀。
 */
static char * fc_generate_function_name(Oid fc_funcid, int fc_nargs, List *fc_argnames, Oid *fc_argtypes,
					   bool fc_has_variadic, bool *fc_use_variadic_p,
					   ParseExprKind fc_special_exprkind)
{
	char	   *fc_result;
	HeapTuple	fc_proctup;
	Form_pg_proc fc_procform;
	char	   *fc_proname;
	bool		fc_use_variadic;
	char	   *fc_nspname;
	FuncDetailCode fc_p_result;
	Oid			fc_p_funcid;
	Oid			fc_p_rettype;
	bool		fc_p_retset;
	int			fc_p_nvargs;
	Oid			fc_p_vatype;
	Oid		   *fc_p_true_typeids;
	bool		fc_force_qualify = false;

	fc_proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcid));
	if (!HeapTupleIsValid(fc_proctup))
		elog(ERROR, "cache lookup failed for function %u", fc_funcid);
	fc_procform = (Form_pg_proc) GETSTRUCT(fc_proctup);
	fc_proname = NameStr(fc_procform->proname);

	/*
	 * 由于解析器的技巧以避免需要保留 CUBE，在某些特殊情况下
	 * 我们需要强制限定。
	 */
	if (fc_special_exprkind == EXPR_KIND_GROUP_BY)
	{
		if (strcmp(fc_proname, "cube") == 0 || strcmp(fc_proname, "rollup") == 0)
			fc_force_qualify = true;
	}

	/*
	 * 确定是否应打印 VARIADIC。我们必须先执行此操作，
	 * 因为它会影响 func_get_detail() 中的查找规则。
	 *
	 * 如果函数具有合并的可变数组参数，我们将始终打印 VARIADIC。
	 * 请注意，对于接受 VARIADIC 参数类型（除 VARIADIC ANY 外）的函数，
	 * 这始终是正确的。如果我们省略 VARIADIC
	 * 并将数组元素作为单独参数打印，则调用可能会
	 * 匹配到较新的非 VARIADIC 函数。
	 */
	if (fc_use_variadic_p)
	{
		/* 解析器不应设置 funcvariadic，除非 fn 是可变参数 */
		Assert(!fc_has_variadic || OidIsValid(fc_procform->provariadic));
		fc_use_variadic = fc_has_variadic;
		*fc_use_variadic_p = fc_use_variadic;
	}
	else
	{
		Assert(!fc_has_variadic);
		fc_use_variadic = false;
	}

	/*
	 * 这里的想法是仅在解析器无法解析给定非限定函数名称时
	 * 进行模式限定，且指定了 argtypes 和 VARIADIC 标志。
	 * 但是如果我们已经决定强制限定，则可以跳过查找，
	 * 假装我们没有找到它。
	 */
	if (!fc_force_qualify)
		fc_p_result = func_get_detail(list_make1(makeString(fc_proname)),
								   NIL, fc_argnames, fc_nargs, fc_argtypes,
								   !fc_use_variadic, true, false,
								   &fc_p_funcid, &fc_p_rettype,
								   &fc_p_retset, &fc_p_nvargs, &fc_p_vatype,
								   &fc_p_true_typeids, NULL);
	else
	{
		fc_p_result = FUNCDETAIL_NOTFOUND;
		fc_p_funcid = InvalidOid;
	}

	if ((fc_p_result == FUNCDETAIL_NORMAL ||
		 fc_p_result == FUNCDETAIL_AGGREGATE ||
		 fc_p_result == FUNCDETAIL_WINDOWFUNC) &&
		fc_p_funcid == fc_funcid)
		fc_nspname = NULL;
	else
		fc_nspname = get_namespace_name_or_temp(fc_procform->pronamespace);

	fc_result = quote_qualified_identifier(fc_nspname, fc_proname);

	ReleaseSysCache(fc_proctup);

	return fc_result;
}

/*
 * generate_operator_name
 *		计算要显示的运算符名称，由 OID 指定，
 *		假设它使用指定的实际参数类型被调用。
 *		（参数类型的重要性在于模糊运算符解析规则。
 *		对于未使用的单目运算符参数，请传递 InvalidOid。）
 *
 * 结果包含所有必要的引用和模式前缀，
 * 加上 OPERATOR() 装饰，以便在表达式中使用一个合格的运算符名称。
 */
static char * fc_generate_operator_name(Oid fc_operid, Oid fc_arg1, Oid fc_arg2)
{
	StringInfoData fc_buf;
	HeapTuple	fc_opertup;
	Form_pg_operator fc_operform;
	char	   *fc_oprname;
	char	   *fc_nspname;
	Operator	fc_p_result;

	initStringInfo(&fc_buf);

	fc_opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(fc_operid));
	if (!HeapTupleIsValid(fc_opertup))
		elog(ERROR, "cache lookup failed for operator %u", fc_operid);
	fc_operform = (Form_pg_operator) GETSTRUCT(fc_opertup);
	fc_oprname = NameStr(fc_operform->oprname);

	/*
	 * 这里的想法是，仅在解析器无法解析给定的非限定运算符名称时
	 * 进行模式限定，且指定了 argtypes。
	 */
	switch (fc_operform->oprkind)
	{
		case 'b':
			fc_p_result = oper(NULL, list_make1(makeString(fc_oprname)), fc_arg1, fc_arg2,
							true, -1);
			break;
		case 'l':
			fc_p_result = left_oper(NULL, list_make1(makeString(fc_oprname)), fc_arg2,
								 true, -1);
			break;
		default:
			elog(ERROR, "unrecognized oprkind: %d", fc_operform->oprkind);
			fc_p_result = NULL;	/* 保持编译器安静 */
			break;
	}

	if (fc_p_result != NULL && oprid(fc_p_result) == fc_operid)
		fc_nspname = NULL;
	else
	{
		fc_nspname = get_namespace_name_or_temp(fc_operform->oprnamespace);
		appendStringInfo(&fc_buf, "OPERATOR(%s.", quote_identifier(fc_nspname));
	}

	appendStringInfoString(&fc_buf, fc_oprname);

	if (fc_nspname)
		appendStringInfoChar(&fc_buf, ')');

	if (fc_p_result != NULL)
		ReleaseSysCache(fc_p_result);

	ReleaseSysCache(fc_opertup);

	return fc_buf.data;
}


/*
 * generate_operator_clause --- 生成一个二元运算符的 WHERE 子句
 *
 * 这用于内部生成和执行的 SQL 查询，其中精度至关重要，可读性是次要的。基本要求是将 "leftop op rightop" 附加到 buf，其中 leftop 和 rightop 被视作字符串，并且假定它们产生 leftoptype 和 rightoptype 类型；运算符通过 OID 进行标识。复杂性来自于需要确保解析器在解析查询时会选择所需的运算符。我们总是使用 OPERATOR(schema.op) 语法来命名运算符，以避免搜索路径的不确定性。如果任何输入不是运算符的输入类型，我们还必须发出类型转换；否则我们将受到解析器在歧义运算符解析中的启发式算法的制约。调用者必须确保 leftop 和 rightop 是类型转换操作的适当参数；如果它们不是变量或参数，最好插入括号。
 */
void generate_operator_clause(StringInfo fc_buf,
						 const char *fc_leftop, Oid fc_leftoptype,
						 Oid fc_opoid,
						 const char *fc_rightop, Oid fc_rightoptype)
{
	HeapTuple	fc_opertup;
	Form_pg_operator fc_operform;
	char	   *fc_oprname;
	char	   *fc_nspname;

	fc_opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(fc_opoid));
	if (!HeapTupleIsValid(fc_opertup))
		elog(ERROR, "cache lookup failed for operator %u", fc_opoid);
	fc_operform = (Form_pg_operator) GETSTRUCT(fc_opertup);
	Assert(fc_operform->oprkind == 'b');
	fc_oprname = NameStr(fc_operform->oprname);

	fc_nspname = get_namespace_name(fc_operform->oprnamespace);

	appendStringInfoString(fc_buf, fc_leftop);
	if (fc_leftoptype != fc_operform->oprleft)
		add_cast_to(fc_buf, fc_operform->oprleft);
	appendStringInfo(fc_buf, " OPERATOR(%s.", quote_identifier(fc_nspname));
	appendStringInfoString(fc_buf, fc_oprname);
	appendStringInfo(fc_buf, ") %s", fc_rightop);
	if (fc_rightoptype != fc_operform->oprright)
		add_cast_to(fc_buf, fc_operform->oprright);

	ReleaseSysCache(fc_opertup);
}

/*
 * 向 buf 添加类型转换规范。我们用繁琐的方式拼写类型名称，
 * 故意不使用 format_type_be()。这是为了避免对于 CHARACTER、BIT
 * 及其他类型的边缘情况，其中使用 SQL 标准语法指定类型会导致不必要的数据截断。
 * 通过这种方式，我们可以确保转换将具有默认的 (-1) 目标 typmod。
 */
static void add_cast_to(StringInfo fc_buf, Oid fc_typid)
{
	HeapTuple	fc_typetup;
	Form_pg_type fc_typform;
	char	   *fc_typname;
	char	   *fc_nspname;

	fc_typetup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_typid));
	if (!HeapTupleIsValid(fc_typetup))
		elog(ERROR, "cache lookup failed for type %u", fc_typid);
	fc_typform = (Form_pg_type) GETSTRUCT(fc_typetup);

	fc_typname = NameStr(fc_typform->typname);
	fc_nspname = get_namespace_name_or_temp(fc_typform->typnamespace);

	appendStringInfo(fc_buf, "::%s.%s",
					 quote_identifier(fc_nspname), quote_identifier(fc_typname));

	ReleaseSysCache(fc_typetup);
}

/*
 * generate_qualified_type_name
 *		计算要显示的由 OID 指定的类型的名称
 *
 * 这与 format_type_be() 的不同之处在于我们无条件地对名称进行模式限定。
 * 这也意味着对 SQL 标准类型名称没有特殊语法……虽然在当前用法中，这应该
 * 仅用于域，因此这类情况无论如何不会发生。
 */
static char * fc_generate_qualified_type_name(Oid fc_typid)
{
	HeapTuple	fc_tp;
	Form_pg_type fc_typtup;
	char	   *fc_typname;
	char	   *fc_nspname;
	char	   *fc_result;

	fc_tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(fc_typid));
	if (!HeapTupleIsValid(fc_tp))
		elog(ERROR, "cache lookup failed for type %u", fc_typid);
	fc_typtup = (Form_pg_type) GETSTRUCT(fc_tp);
	fc_typname = NameStr(fc_typtup->typname);

	fc_nspname = get_namespace_name_or_temp(fc_typtup->typnamespace);
	if (!fc_nspname)
		elog(ERROR, "cache lookup failed for namespace %u",
			 fc_typtup->typnamespace);

	fc_result = quote_qualified_identifier(fc_nspname, fc_typname);

	ReleaseSysCache(fc_tp);

	return fc_result;
}

/*
 * generate_collation_name
 *		计算要显示的由 OID 指定的排序规则的名称
 *
 * 结果包括所有必要的引号和模式前缀。
 */
char * generate_collation_name(Oid fc_collid)
{
	HeapTuple	fc_tp;
	Form_pg_collation fc_colltup;
	char	   *fc_collname;
	char	   *fc_nspname;
	char	   *fc_result;

	fc_tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(fc_collid));
	if (!HeapTupleIsValid(fc_tp))
		elog(ERROR, "cache lookup failed for collation %u", fc_collid);
	fc_colltup = (Form_pg_collation) GETSTRUCT(fc_tp);
	fc_collname = NameStr(fc_colltup->collname);

	if (!CollationIsVisible(fc_collid))
		fc_nspname = get_namespace_name_or_temp(fc_colltup->collnamespace);
	else
		fc_nspname = NULL;

	fc_result = quote_qualified_identifier(fc_nspname, fc_collname);

	ReleaseSysCache(fc_tp);

	return fc_result;
}

/*
 * 给定一个 C 字符串，生成一个 TEXT 数据项。
 *
 * 我们假设输入是 palloc 的，并且可能会被释放。
 */
static text * fc_string_to_text(char *fc_str)
{
	text	   *fc_result;

	fc_result = cstring_to_text(fc_str);
	pfree(fc_str);
	return fc_result;
}

/*
 * 从 text[] 数据项生成一个表示关系选项的 C 字符串。
 */
static void get_reloptions(StringInfo fc_buf, Datum fc_reloptions)
{
	Datum	   *fc_options;
	int			fc_noptions;
	int			fc_i;

	deconstruct_array(DatumGetArrayTypeP(fc_reloptions),
					  TEXTOID, -1, false, TYPALIGN_INT,
					  &fc_options, NULL, &fc_noptions);

	for (fc_i = 0; fc_i < fc_noptions; fc_i++)
	{
		char	   *fc_option = TextDatumGetCString(fc_options[fc_i]);
		char	   *fc_name;
		char	   *fc_separator;
		char	   *fc_value;

		/*
		 * 每个数组元素应具有 name=value 的形式。如果 "=" 因某种原因缺失， 
		 * 则将其视为一个空值。
		 */
		fc_name = fc_option;
		fc_separator = strchr(fc_option, '=');
		if (fc_separator)
		{
			*fc_separator = '\0';
			fc_value = fc_separator + 1;
		}
		else
			fc_value = "";

		if (fc_i > 0)
			appendStringInfoString(fc_buf, ", ");
		appendStringInfo(fc_buf, "%s=", quote_identifier(fc_name));

		/*
		 * 一般来说，我们需要引用该值；但为了避免不必要的
		 * 混乱，如果它是一个不需要引用的标识符则不引用。
		 * （我们也可以允许数字，但这比看起来更棘手——例如，前导零是否显著？
		 * 我们不想在这里对自定义 reloptions 的含义做出过多假设。）
		 */
		if (quote_identifier(fc_value) == fc_value)
			appendStringInfoString(fc_buf, fc_value);
		else
			fc_simple_quote_literal(fc_buf, fc_value);

		pfree(fc_option);
	}
}

/*
 * 生成一个表示关系的 reloptions 的 C 字符串，如果没有则返回 NULL。
 */
static char * fc_flatten_reloptions(Oid fc_relid)
{
	char	   *fc_result = NULL;
	HeapTuple	fc_tuple;
	Datum		fc_reloptions;
	bool		fc_isnull;

	fc_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for relation %u", fc_relid);

	fc_reloptions = SysCacheGetAttr(RELOID, fc_tuple,
								 Anum_pg_class_reloptions, &fc_isnull);
	if (!fc_isnull)
	{
		StringInfoData fc_buf;

		initStringInfo(&fc_buf);
		get_reloptions(&fc_buf, fc_reloptions);

		fc_result = fc_buf.data;
	}

	ReleaseSysCache(fc_tuple);

	return fc_result;
}

/*
 * get_range_partbound_string
 *		范围分区边界的 C 字符串表示
 */
char * get_range_partbound_string(List *fc_bound_datums)
{
	deparse_context fc_context;
	StringInfo	fc_buf = makeStringInfo();
	ListCell   *fc_cell;
	char	   *fc_sep;

	memset(&fc_context, 0, sizeof(deparse_context));
	fc_context.buf = fc_buf;

	appendStringInfoChar(fc_buf, '(');
	fc_sep = "";
	foreach(fc_cell, fc_bound_datums)
	{
		PartitionRangeDatum *fc_datum =
		lfirst_node(PartitionRangeDatum, fc_cell);

		appendStringInfoString(fc_buf, fc_sep);
		if (fc_datum->kind == PARTITION_RANGE_DATUM_MINVALUE)
			appendStringInfoString(fc_buf, "MINVALUE");
		else if (fc_datum->kind == PARTITION_RANGE_DATUM_MAXVALUE)
			appendStringInfoString(fc_buf, "MAXVALUE");
		else
		{
			Const	   *fc_val = castNode(Const, fc_datum->value);

			get_const_expr(fc_val, &fc_context, -1);
		}
		fc_sep = ", ";
	}
	appendStringInfoChar(fc_buf, ')');

	return fc_buf->data;
}
